PDA

Просмотр полной версии : Менеджер памяти для многозадачной ОС



GriV
21.03.2005, 13:57
Суть такая:

1) ни одна программа не может работать напрямую с памятью, исключение составляет память из Heap, но в любом случае этот адрес идёт от системы, система указывает программе где находится её heap.

2) программа резервирует любое количество памяти с точностью до байта трёхбайтовым указателем.

3) Разделяются блоки данных (которые хранятся где угодно) и программа (тело и heap), последнее всегда доступно прямым обращением (п. 1).

4) Блоки данных необходимо запрашивать у системы:
4.А) New(p: pointer, Length [3 байта], Mode [режим Share - только для владельца или для всех ]) - выделение блока памяти.
4.Б) GetReadMem(p: pointer, OffSet: pointer[3 байта], Length [2 байта]) - получение памяти на чтение, перед получением памяти данный блок копируется из верхней области в окно обмена.
4.В) GetRWMem(синтаксис аналогично 4.б) - получение памяти на запись, после того как данный кусок обработан он копируется в оригинальное место расположения.
4.Г) EndGetReadMem(p: pointer) - освобождается окно обмена, чтение из области данных закончена.
4.Д) EndGetRWMem(p: pointer) - освобождается окно обмена область копируется в исходное место.

5) Адрес окна возвращается системой (менеджером памяти).

6) В случае невозможности выделить блок возвращается сообщение об ошибке.

GriV
21.03.2005, 14:35
для некоторых случаев не всегда имеется возможность определить сколько памяти надо
Поэтому имеется функция
realloc(p: pointer, length [3 байта], Mode) - перераспределяет память под имеющийся дескриптор.

dwt
21.03.2005, 14:36
2) программа резервирует любое количество памяти с точностью до байта трёхбайтовым указателем.
(Page)+(Address)?

GriV
21.03.2005, 14:51
(Page)+(Address)?

Нет, именно три байта.
Все проблемы с трансляцией трёхбайтового адреса на страницы на себя берёт система (менеджер).

Хотя если разбивать как Page*65536+Address, то наверное да

dwt
21.03.2005, 15:04
Нет, именно три байта.
Все проблемы с трансляцией трёхбайтового адреса на страницы на себя берёт система (менеджер).
А не вызовет ли эта трансляция излишних "тормозов"? Ведь, трансляция прямой трехбайтовой адресации (16MB) - дело хоть и не очень многотактовое, но как мне кажется, полная инкапсуляция "в систему" процедур обработки памяти в целом выйдет довольно-таки тактозатратным (всяческие проверки, арифметика, и т.д.).
Я имел ввиду (номер page16k)+(физический адрес).

GriV
21.03.2005, 16:11
да, в любом случае такая система предполагает притормаживание приложений/процессов из-за того, что происходит служебная работа (перемещение блоков памяти)
НО: такая система даёт плюс в использование виртуальной памяти ЛЮБОГО размера (до 16 метров) для блоков данных для КАЖДОГО процесса.

И ещё, при такой организации возможно при помощи виртуальной памяти "расширять" память для 48кб до 48кб+размер своп-файла на диске (или дискете) :)

dwt
21.03.2005, 16:19
да, в любом случае такая система предполагает притормаживание приложений/процессов из-за того, что происходит служебная работа (перемещение блоков памяти)
НО: такая система даёт плюс в использование виртуальной памяти ЛЮБОГО размера (до 16 метров) для блоков данных для КАЖДОГО процесса.
А цель? Цель оправдывает средства?

GriV
21.03.2005, 16:22
А цель? Цель оправдывает средства?

Цель в этом случае оправдывает средства.

Программист не сможет просчитать все варианты использования памяти, система же будет просчитывать всё автоматически и приложения писать становится до смешного просто: указывайте что надо использовать память, связывайте индекс и ваяйте!

Vladimir Kladov
21.03.2005, 16:26
Не вижу причин обойтись 2х-байтным дескриптором, тем не менее, даже если память выделяется с точностью до 1 байта. Дескритор - это номер в таблице (или связном списке) выделенных блоков.

dwt
21.03.2005, 16:27
Цель в этом случае оправдывает средства.

Программист не сможет просчитать все варианты использования памяти, система же будет просчитывать всё автоматически и приложения писать становится до смешного просто: указывайте что надо использовать память, связывайте индекс и ваяйте!
А не вызовет ли это необходимость создания языка выского уровня или хотя бы интерфейса для обработки не присущих z80 команд работы с памятью/адресов? В последнем случае команды/адреса будут передаваться посредством дека/стека/очереди/...?

dwt
21.03.2005, 16:29
Не вижу причин обойтись 2х-байтным дескриптором, тем не менее, даже если память выделяется с точностью до 1 байта. Дескритор - это номер в таблице (или связном списке) выделенных блоков.
В принципе, наверное, это выход из ситуации. Однако, наверное, необходимо держать эту таблицу "открытой", чтобы не вызвать излишней централизации.

GriV
21.03.2005, 16:31
Сам дескриптор имеет длину два байта (т.е. всего теоретически возможно 65536 процессов и из областей), но адрес задаётся тремя байтами не путайте плиз

GriV
21.03.2005, 16:36
А не вызовет ли это необходимость создания языка выского уровня или хотя бы интерфейса для обработки не присущих z80 команд работы с памятью/адресов? В последнем случае команды/адреса будут передаваться посредством дека/стека/очереди/...?

Чисто технически отвечу так: запрос может передаваться КАК УГОДНО (любой из указанных вариантов) текущий менеджер использует конструкцию

Call New
Defb <Старший_байт>
Defw <Слово_младшей части_адреса>

на выходе рутина даёт BC= номер дескриптора

dwt
21.03.2005, 16:42
Чисто технически отвечу так: запрос может передаваться КАК УГОДНО (любой из указанных вариантов) текущий менеджер использует конструкцию

Call New
Defb <Старший_байт>
Defw <Слово_младшей части_адреса>

на выходе рутина даёт BC= номер дескриптора
В случае, если ядро ОС будет находится в ПЗУ, то:
RST#XX
Defb <Старший_байт>
Defw <Слово_младшей части_адреса>?
То есть аргументы будут читаться и использованием стека? Довольно медленно. Не предусмотреть ли альтернативу через регистры? Например: в A - <Старший_байт>, в HL - <Слово_младшей части_адреса>.

GriV
21.03.2005, 16:47
Чисто технически делается КАК УГОДНО, есть и такой вариант который ты предлагаешь, его можно внедрить конешно... Если будет такое желание...

Vitamin
21.03.2005, 23:20
В случае, если ядро ОС будет находится в ПЗУ, то:
RST#XX
Defb <Старший_байт>
Defw <Слово_младшей части_адреса>?
То есть аргументы будут читаться и использованием стека? Довольно медленно. Не предусмотреть ли альтернативу через регистры? Например: в A - <Старший_байт>, в HL - <Слово_младшей части_адреса>.
немного отступлю от темы, надо будет отдельный тред имхо. народ справедливо возмущался по поводу ресурсоемкости некоторых процедур и в то же время считает что именно с пзу стоит общаться только через рестарты. это ж геморрой! во-первых рестартов очень мало, требуется индексация. поэтому лучше делать прямые вызовы целевых функций.

random
22.03.2005, 00:23
предлагаю все стандартные функции встроить в ОСь таким образом чтобы можно было их вызывать по определенному оффсету с например 0х10 из ПЗУ. по четыре байта на функцию. (оффсет функции и оффсет дескриптора). так потом можно будет в асме делать хедер по которому вызывать через LD HL, Х: CALL (HL). ну с определенными параметрами. так можно и "сигнатуру" проверить на функцию если есть желание, для пущей совместимости.

GriV
22.03.2005, 06:56
предлагаю все стандартные функции встроить в ОСь таким образом чтобы можно было их вызывать по определенному оффсету с например 0х10 из ПЗУ. по четыре байта на функцию. (оффсет функции и оффсет дескриптора). так потом можно будет в асме делать хедер по которому вызывать через LD HL, Х: CALL (HL). ну с определенными параметрами. так можно и "сигнатуру" проверить на функцию если есть желание, для пущей совместимости.

Замечательно, только пожалуйста, по теме %SUBJ%

dwt
22.03.2005, 09:55
Чисто технически делается КАК УГОДНО, есть и такой вариант который ты предлагаешь, его можно внедрить конешно... Если будет такое желание...
Чтобы не было такого, что кто-то говороит не в тему, или "не читает внимательно", "разжуй", пожалуйста, то, как и что ты хочешь сделать КОНКРЕТНО.

dwt
22.03.2005, 09:58
народ справедливо возмущался по поводу ресурсоемкости некоторых процедур и в то же время считает что именно с пзу стоит общаться только через рестарты. это ж геморрой! во-первых рестартов очень мало, требуется индексация. поэтому лучше делать прямые вызовы целевых функций.
Никто не говорил, что необходимо делать ТОЛЬКО через rst. Их нужно предусмотреть на отдельные частоиспользуемые КЛАССЫ функций. И, естественно, предусмотреть отдельные точки вызовов целевых функций.

acidrain
22.03.2005, 11:49
предлагаю все стандартные функции встроить в ОСь таким образом чтобы можно было их вызывать по определенному оффсету с например 0х10 из ПЗУ. по четыре байта на функцию. (оффсет функции и оффсет дескриптора). так потом можно будет в асме делать хедер по которому вызывать через LD HL, Х: CALL (HL). ну с определенными параметрами. так можно и "сигнатуру" проверить на функцию если есть желание, для пущей совместимости.

Ну не знаю, имо - капание гроба для самих себя. Не надо рст, не надо пзу. Надо делать разумно. Вот амига 500 с процем в 7,14 МГц так там многозадачная ось и продуманная (правда там спец процы есь), не то что Мы тут пытаемя сделать.
Проблема вся в том, что каждый тянет одеяло на себя и пытается сделать для себя любимого (и я в их числе;)). Я предлагаю найти компромис.

random
22.03.2005, 12:01
не понимаю как удобство вызова стандартных функций (например strlen или memcpy или malloc) означает копание гроба? как это не надо ПЗУ? просто нижнюю память? и так ее всего 64К!

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

ответь по теме, чем плохо иметь доступные функции в ПЗУ?

random
22.03.2005, 12:10
Никто не говорил, что необходимо делать ТОЛЬКО через rst. Их нужно предусмотреть на отдельные частоиспользуемые КЛАССЫ функций. И, естественно, предусмотреть отдельные точки вызовов целевых функций.

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

главное написать грамотный header к этим фунцкиям, с пояснениями параметров и формата ответов.

например предположим:

; функция strlen.
; считает длину строки (null terminated)
; HL = адрес строки
; возвращает:
; в HL = длина строки в символах

strlen jp (0x20)

; функция malloc.
; выделяет блок памяти для программы
; HL = длина блока памяти в байтах
; возвращает:
; в HL = адрес выделенного блока памяти
; если HL==0 памяти недостаточно

malloc jp (0x21)

и так далее

тогда вызов элементарен

...
ld hl, stroka
call strlen
...

lvd
22.03.2005, 13:58
не понимаю как удобство вызова стандартных функций (например strlen или memcpy или malloc) означает копание гроба? как это не надо ПЗУ? просто нижнюю память? и так ее всего 64К!


Вот мне интересно, с каких пор strlen и memcpy стали функциями ОС? Всегда были функциями сёвыми, и были даже там, где оси нету как класса.

dwt
22.03.2005, 16:58
Вот мне интересно, с каких пор strlen и memcpy стали функциями ОС? Всегда были функциями сёвыми, и были даже там, где оси нету как класса.
Я думаю, что просто такая формализация. Менеджмент памяти на низком уровне (имеется ввиду выделение физической памяти с физическими адресами, и соответственно их трансляция в прямой адрес) - это прерогатива именно ОС. Да и речь идет именно о многозадачной ОС!

random
22.03.2005, 17:38
Вот мне интересно, с каких пор strlen и memcpy стали функциями ОС? Всегда были функциями сёвыми, и были даже там, где оси нету как класса.
на самом деле я взял эти примеры с потолка. все равно внутри ОСи эти фунцкии будут необходимы. почему их не открыть для пользователей/программ? все равно программа будет совместима только с этой осью :)

Vitamin
22.03.2005, 18:14
насчет memcpy и проч.
данные функции просто необходимо включить в ядро по той простой причине, что ядро предоставляет свой интерфейс доступа к памяти. они будут работать с дескрипторами блоков, осуществлять самые быстрые алгоритмы, возможно даже с использованием аппаратных средств. для работы в локальной памяти конечно хватит ldir и иже с ним.

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

random
22.03.2005, 19:11
таблица необходима для совместимости! чтобы программы все не пересобирать когда будут явные системные изменения!!!

Vitamin
22.03.2005, 19:58
таблица необходима для совместимости! чтобы программы все не пересобирать когда будут явные системные изменения!!!
читай внимательней предыдущий пост. не компилятор будет подставлять адреса, а СИСТЕМА. т.е. начихать что там адреса физические сменились. лишь бы порядок нумерации не менялся (на нем основывается подстановка)

random
22.03.2005, 20:08
ты чего, предлагаешь делать DLL сразу же? не быстровато ли? по любому будет проще систему собирать если у самой системы есть общая таблица всех вызовов. и не 4 байта а всего 2, если не хотите версии проверять.

Vitamin
22.03.2005, 20:34
ты чего, предлагаешь делать DLL сразу же? не быстровато ли? по любому будет проще систему собирать если у самой системы есть общая таблица всех вызовов. и не 4 байта а всего 2, если не хотите версии проверять.
в длл немного по другому- адреса вычисляются динамически по символическим именам. а насчет общей таблицы вызовов- я имел в виду ее. т.е. приложение грузится в память и начинает настраиваться под конкретный адрес- вот сюда занести текущий адрес с таким-то смещением, а вот сюда занести адрес такого-то системного вызова. по номеру вызова берем адрес процедуры и пихаем адрес в код.

Vladimir Kladov
22.03.2005, 20:43
лично мне нравится вызов системных функций через RST - это сокращает код, на 1 байт на вызов как минимум, и неважно, что на интерпретацию кода уходит еще несколько тактов. И одновременно решает вопрос независимости от версии осы - лишь бы номера функций совпадали. А загрузчику можно было бы доверить подгонку адресов в самой загружаемой программе "по месту", не всегда в Z80 удается делать код перемещаемым без само-подгонки. Вот на ось и можно было было бы положиться.

Vitamin
22.03.2005, 21:19
лично мне нравится вызов системных функций через RST - это сокращает код, на 1 байт на вызов как минимум, и неважно, что на интерпретацию кода уходит еще несколько тактов. И одновременно решает вопрос независимости от версии осы - лишь бы номера функций совпадали.
что короче:
ld a,func_number
rst N

или

call NNN
?

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

Spectre
22.03.2005, 22:29
что короче:
ld a,func_number
rst N

или

call NNN
?

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

А если метод которому уже почти 25 лет?

RST n
DB n

Vitamin
22.03.2005, 23:10
А если метод которому уже почти 25 лет?

RST n
DB n
лучше только по памяти. затраты на интерпретацию команды больше, потому что надо считать код команды, скорректировать адрес возврата. да и регистров при этом больше используется

Alex/AT
22.03.2005, 23:31
Мне все-таки наболее очевидным кажется встраивание в таблицу релокаций указателей вызовов. Т.е. ось при загрузке бинарника не только корректирует адреса релокаций, но и подставляет адреса системных функций. Тут Vitamin абсолютно прав. Как результат - избегаем необходимости трансляции команд при выполнении и код получается 3 байта на вызов (RST+один байт все же не катит, ибо функций явно будет >256). А параметры передавать на регистрах, в строгом порядке.

Spectre
23.03.2005, 11:26
лучше только по памяти. затраты на интерпретацию команды больше, потому что надо считать код команды, скорректировать адрес возврата. да и регистров при этом больше используется

Ну это просто смешно. Сначала попробуйте, а потом утверждайте:

EX (SP),HL
LD reg,(HL)
INC HL
EX (SP),HL

Целых 4 байта, целый 51 такт! Регистр используется только 1, какой - на ваш выбор.

Даже обычный RST #10 от момента вызова до момента передачи управления указанной нами подпрограмме расходует только 76 тактов (без учета самого RST)! Правда портит альтернативный DE при этом.

p.s. Я вовсе не сторонник метода RST (отлаживать не очень удобно), но просто ради справедливости.

lvd
23.03.2005, 12:11
Мне все-таки наболее очевидным кажется встраивание в таблицу релокаций указателей вызовов. Т.е. ось при загрузке бинарника не только корректирует адреса релокаций, но и подставляет адреса системных функций. Тут Vitamin абсолютно прав. Как результат - избегаем необходимости трансляции команд при выполнении и код получается 3 байта на вызов (RST+один байт все же не катит, ибо функций явно будет >256). А параметры передавать на регистрах, в строгом порядке.

А вот интересно, что же никто не додумался сделать у оси таблицу типа jp a:jp b:jp c:jp etc.
Адреса системных функций - адреса этих жпшников. Которые не меняются при смене версий.

random
23.03.2005, 13:10
lvd. именно про это я и говорил.

lvd
23.03.2005, 14:47
lvd. именно про это я и говорил.
Угу, сорри, невнимательно читал =)

Alex/AT
23.03.2005, 17:37
lvd: если у оси будет допустим 1024 функции, то это аж 3 килобайта на одну таблицу переходов... немножко расточительно ;) если просто таблицу функций, где точки входа хранить дельтами от предыдущей точки входа (-64..63 байт - 0xxxxxxx, до -16384..16383 - 1xxxxxxx, 0xxxxxxx [принцип думаю понятен]), то памяти уйдет куда меньше. Настройка прог будет тоооормозная, но во время загрузки вполне нормально...

Spectre
23.03.2005, 17:54
lvd: если у оси будет допустим 1024 функции, то это аж 3 килобайта на одну таблицу переходов... немножко расточительно ;) если просто таблицу функций, где точки входа хранить дельтами от предыдущей точки входа (-64..63 байт - 0xxxxxxx, до -16384..16383 - 1xxxxxxx, 0xxxxxxx [принцип думаю понятен]), то памяти уйдет куда меньше. Настройка прог будет тоооормозная, но во время загрузки вполне нормально...

Какие 1024 функции?! Хоть бы сотня набралась...

Vitamin
23.03.2005, 19:58
Ну это просто смешно. Сначала попробуйте, а потом утверждайте:

EX (SP),HL
LD reg,(HL)
INC HL
EX (SP),HL

Целых 4 байта, целый 51 такт! Регистр используется только 1, какой - на ваш выбор.

ну вот ты и доказал- по сравнению с
ld reg,N
rst M

и

rst M
db N

разницы 4 байта и 51 такт %)
а если точнее, то 1 байт выгоды на каждый вызов и 44 такта проигрыша (это с учетом загрузки параметра и доп. обвязки)



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

GriV
24.03.2005, 10:47
Всего два дня меня не было...

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

Насчёт вызовов: имеется два подхода

1) Работа с Call и трансляцией этих вызовов под адреса функций системы в процессе инициализации процесса/приложения
+: Скорость работы приложения, выигрыш в скорости прямопропорционален количеству попаданий данных вызовов в теле программы
-: Система должна подстраивать тело процесса (релоцировать его) под вызов функций
Замечание: Если вы взгляните на zxdocs.fatal.ru в резделе кодинг модульную структуры вы увидите, что уже есть разработанный формат приложений, который задаёт как точки релокации внутренние, так и точки релокации внешние (т.е. вызов внешних процедур, например, процедур системы). Имеено такая модульная система является прототипом приложения для многозадачной ОС.

2) Работа через классические вызовы с обвязкой в виде Defb Defw до/после RST/Call и обвязкой через регистры
+: Очень удобно писать программы с вызовом процедур из ПЗУ
-: Необходимо транслировать, что является классом функций в передаваемых параметрах, что является подтипом функций в передаваемых параметрах, на что требуется опять же работа системы, однако такая работа будет производится при каждом вызове, чем больше вызовов в теле программы, тем больше затраты системы на трансляцию таких вызовов (тоже проямопропорционально)
Замечание: такой метод имеет право жить хотя бы потому, что он используется, как заметил коллега, уже 25 лет. Однако, в этом варианте код команды (вместе с обвязкой, в байтах и в тактах) вообще говоря увеличивается, т.к. теперь класс команды и подтип указываются уже в обвязке а не прямым указанием.

Ну теперь на таком сравнительном представлении каждого из методов станет ясно почему мы с Витамином выбрали именно первый способ представления системных вызовов...

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

неправда, никаких обвязок в DEFB/DEFW после вызова не делается. про RST уже забыли, используется структура типа CALL malloc, где malloc: defb 0x, а по адресу 0х в пзу записано JP XXXX.

все остальное в параметрах. скорость исполнения практически равна прямому вызову в теле программы.

какие классы?

Vitamin
24.03.2005, 16:06
Имеено такая модульная система является прототипом приложения для многозадачной ОС.

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

Vitamin
24.03.2005, 16:08
неправда, никаких обвязок в DEFB/DEFW после вызова не делается. про RST уже забыли, используется структура типа CALL malloc, где malloc: defb 0x, а по адресу 0х в пзу записано JP XXXX.

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



какие классы?
классы функций- работа с памятью, работа с процессами и т.д.

GriV
24.03.2005, 16:30
неправда, никаких обвязок в DEFB/DEFW после вызова не делается. про RST уже забыли, используется структура типа CALL malloc, где malloc: defb 0x, а по адресу 0х в пзу записано JP XXXX.

все остальное в параметрах. скорость исполнения практически равна прямому вызову в теле программы.

какие классы?

Классами называются принципиально разные функции системы (для работы с задачами, файлами и т.д.). Если посмотреть то, что ты предлагаешь то вместо обвески из Defb и Defw получается обвеска из Jp, да причём если там всё могло обойтись двумя байтами Defb для указания класса и подкласса функции, то здесь же тратится совершенно напрасно сразу три байта... Про это собственно и речь...

GriV
24.03.2005, 16:39
2Random> и потом, здесь реализуется подход открытых точек системы.
Т.е. например, разработчик системы (скажем это Vitamin) пишет, что для переключения банков памяти используется точка системы mem_bank_changer. Тогда система смотрит при загрузке приложения/процесса и автоматом подменяет указанную переменную (в нужном месте конечно), прописав вместо Call 0 - Call #1234, где #1234 - адрес той самой mem_bank_changer.
Однако, если система не знает такой внутренней для себя и внешней для приложения точки вызова, то она просто не запустит приложение, указав на неизвестный метод вызова.
С другой стороны, при фиксированной ссылке Call -> Jp при расширении системы просто может запросто произойти такой случай, что базовых точек может не хватить (неверно составлена таблица функций, требуется перераспределение, очень реальная ситуация, часто встречается на практике). Тогда все приложения надо переписывать, надо смотреть какая версия приложения, под какую версию ОСь и т.д.
Мне кажется из указанного примера очевидна перспективность конструкций с ретрансляцией системных вызовов, согласно импортируемых точек, по сравнению с той что предлагаешь ты.

Vladimir Kladov
24.03.2005, 16:54
соглашусь с тем, что call adr+k*N самый быстрый и простой для компилятора (не надо никаких прибамбасов в компиляторе) способ. Могу предложить способ "настройки" вызывающей функции "по месту":

org 100h
call adjust_jp
defw function1

org 105h
call adjust_jp
defw function2

;...
adjust_jp proc ; adjust caller and jump to function
ex (sp),hl ; hl->defw function1, sp->hl,retaddr
push de ; hl->defw function1, sp->de,hl,retaddr
ld e,(hl):inc hl:ld d,(hl) ; de=function1
ex de,hl ; hl=function1
push ix
ld ix,0 : add ix,sp ; ix=sp->ix,de,hl,retaddr
ld e,(ix+6) : ld d,(ix+7) ; de=retaddr
ex de,(ix) ; ix=retaddr
ld (ix-1),h : ld (ix-2),l ; caller adjusted!
pop ix : pop de
ex (sp),hl ; sp->function1,retaddr
ret

В этом варианте функции идут с адреса 100h с шагом 5. Но если под вызов настройщика выделить rst, то можно сделать с шагом 3. Получается, что программа вызывает системные ф-ции изначально по заранее заданным адресам (100h, 103h, ...), а при каждом вызове происходит автоподстройка, и в следующий раз этот же вызов уже напрямую вызывает function1. Да, использоваться в этом случае могут call cond, а не только call. К сожалению jp работать не будет корректно.

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

Vladimir Kladov
24.03.2005, 18:21
На самом деле, я не вижу причин, почему надо выбирать только один и самый быстрый при этом способ вызова системных функций. Надо подходить к этому избирательно. Для часто вызываемых функций, скорость работы которых не столь важна, подошли бы RST. Для прямого и очень при этом быстрого вызова простая таблица с jp f1;jpf2;... самое нормальное. Предолженный выше мной вариант на самом деле не так хорош например в случае, когда быстрая работа функции требуется уже при первом вызове. А вместо этого первый вызов начинает выполнять всю перечисленную галиматью, теряя драгоценные такты. Еще надо посчитать, сколько раз в программе должна вызваться эта функция, чтобы за счет экономии 10 тактов на jp окупить тот расход, который был съеден на первом обращении.

Предлагаю подумать больше не о способе вызова системных функций, а о способе автоподстройки "приложений" по месту загрузки. У меня вот такая идейка есть. Выделить RST под операцию подстройки, которая бы подстраивала 2хбайтное слово через 1 байт после вызова. Так:

rst R ; R-заранее заданное любое 1..6
defb ?;мнемоника операции
defw ?;подстраиваемый адрес

Все что нужно знать подстройщику - адрес, на который настроена программа и адрес, с которого она загружена. Точнее разницу.
Подстраиваться может адрес практически в любой команде. Еще подстройщик может анализировать следующий байт за вызовом rst на наличие префиксов dd/fd и подстраивать тогда слово еще на байт дальше.
По окончании подстройки сам вызов RST R заменяется на nop, чтобы подстройка больше не вызывалась для этого адреса.
Преимущества: дополнительная таблица не нужна, годится любой имеющийся компилятор с асма, подстройка выполняется по мере надобности, загружать программу можно с любого байта в ОЗУ (кроме области занятой под систему и стек, разумеется).
Недостаток: +1 байт в программе на каждую команду требующую подстройки. В принципе, эти байты можно задействовать иногда для самомодифицирующегося кода, но это уже для спецов.

Попробую привести код:

org R*8
ex (sp),ix
push hl
push de
push af
jp reloc_1

reloc_1:
ld de,(reloc_difference)
ld a,(ix+0)
and a,0DFh
cp a,0DDh
push af
jr nz,@1
inc ix
@1:ld L,(ix+1):ld H,(ix+2)
add hl,de
ld (ix+1),L:ld (ix+2),H
pop af
jr nz,@2
dec ix
@2:ld (ix-1),0
pop af
pop de
pop hl
ex (sp),ix
ret

Vladimir Kladov
24.03.2005, 18:27
2vitamin. а при чем там реентерабельность? сначала стояло call 103h, после первого срабатывания на этом месте уже стоИт call fun2. Никакие регистры предложенный выше способ не портит. Но он длинный и эффект от него - только при большом и частом вызове подстраиваемой таким образом функции, о чем я уже сказал в предыдущем посте.

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

Vitamin
24.03.2005, 19:31
бяка будет если функция прервется после того, как часть адреса будет скорректирована и будет вызов второй раз. тогда программа улетит в нирвану. и вместе с ней вся ось.

насчет релокации смотри http://zxdocs.fatal.ru/coding/module.zip

GriV
24.03.2005, 19:50
2Vladimir Kladov> Универсальный метод не "канает" только потому что не факт что система находится в ПЗУ. Вообще говоря, система может быть где угодно, и в указанном месте (в местах ретрансляции) будут тоже находится трапы, но хитрые: если это большая и долгая функция - то переход на другую страницу ОЗУ и т.д, если это быстрая и ответственная фукнкция, то в нижней части ОЗУ.
А вызов системных функций - дело тонкое, потому лучше именно через Call напрямую согласно указанному методу.

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

Vladimir Kladov
24.03.2005, 20:06
я так и подумал. Но мне кажется, так быть не должно. Даже если делать на спеке многозадачную систему, я полагаю, каждая задача должна оставаться однопоточной и тогда никаких проблем с реентерабельносью. Если ось прерывает задачу, чтобы отдать кадр другой задаче, она должна будет продолжить эту задачу только с этого самого места, и ни с какого другого.

А если это не так, то оба мои эссе неприменимы. Может быть, для такого конкретно случая. Только не очень ясно-понятно, что это за случай такой. Например, драйвер мыши, который может вызываться "в контексте" любой задачи? Хм. Очень даже похоже, если сам драйвер как загружаемый кусок кода, а не часть оси. Но драйвер такого рода мог бы работать в запрещенных прерываниях. Тогда страшен только случай NMI.

В общем, мой код #1 для автоподстройки для эфемерной экономии 10 тактов просто наглядно демонстрирует ненужность такого израта, и очевидное преимущество простой таблиы из jp f1;jp f2; .. Такая таблица кстати, делались в "оси" на микроше. В 1983-84 г. будучи на службе в СА я принимал участие в разработке микрошеподобной машины на базе кр580 или как он там назывался. На мне был весь софт (ось, ассемблер, драйверы устройств, прикладное по). За 3 месяца все это было сбацано, и представляло из себя многозадачную (!) ось, которая могла одновременно обслуживать несколько периферийных устройств (часы точного времени, перфосчитывать 1500 байт/с, перфоратор (десятки байт /с), телетайп, быстрый принтер (IBM-какой-то, матричный принтер, до 10 строк /с), модем 9600 бод (прием онли), клавиатуру от дисплея IBM/360. Экран был ч/б, но графический, 640х384 или 80х24 символа матрицей 16х8. Я мог одновременно компилировать, перфорировать, печатать, набирать текст в редакторе... Да, когда шел прием с модема, все останавливалось, тут скорости уже не хватало. Хватало только на экран выводить мозаику. Да, отвлекся, мой первый "Осьминог" припомнил... Интересно, чем сейчас Марат Вениаминович занимается (не буду называть фамилию, неказисто она звучит по-русски :) ), и где прочие участники той команды.

Код №2 больше пригож для случая утилит и прикладных задач, тоже не очень похоже, что таким способом должны пользоваться драйверы или какие-то другие вещи, требующие реентерабельности. На крайняк, можно было бы подумать о том, чтобы система прежде чем забирать кадр у задачи, анализировала бы, что там сейчас делается, и если выполняется потенциально опасный участок кода из самой системы, просто не пыталась прервать исполнение. Самое простое, что приходит в голову.

Vladimir Kladov
24.03.2005, 20:46
скачал, посмотрел. Очень все серьезно (не многовато кода?). Я бы делал проще :) Но интересно посмотреть, что тут получится. Ассемблер должен поддерживать макросы, как минимум, и не просто макросы, а переменные. Асм без макросов не прокатит. Сдругой строны, таблицы не занимают место и могут выбрасываться после того, как отработаны, что конечно, гуд. Сразу линковщик, с именами. Ну что ж, правда интересно становится, что получится.

Vitamin
24.03.2005, 21:38
скачал, посмотрел. Очень все серьезно (не многовато кода?). Я бы делал проще :) Но интересно посмотреть, что тут получится. Ассемблер должен поддерживать макросы, как минимум, и не просто макросы, а переменные. Асм без макросов не прокатит. Сдругой строны, таблицы не занимают место и могут выбрасываться после того, как отработаны, что конечно, гуд. Сразу линковщик, с именами. Ну что ж, правда интересно становится, что получится.
ассемблеры сейчас все с макросами делают. без них работа на порядок труднее.
а че там кода много? только самое необходимое! и что бы ты упростил? поделись идеями %)

Vladimir Kladov
25.03.2005, 04:59
А вообще, ваш метод годится при наличии небольшой доп. утилиты для любого компилятора, хоть даже и с паскаля или С. Достаточно откомпилировать с 2х разных адресов, и подать на вход такой утилиты, и совсем несложно выявить слова, которые отличаются на смещение, которое должно наблюдаться (start2-start1).

Alex/AT
25.03.2005, 07:49
Ээээ. Товарищи, какой RST? Давайте подумаем о том, что стандартное ПЗУ мы модифицировать пока не собираемся ;) Иначе толку от этой ОСи на реальных машинах? Конечно, "кеш" есть, но все-таки совместимость бы надо оставить...

И еще - зачем автоподстройка? Именно подстройка при загрузке. Тогда и механизм DLL реализуется "на ура".

Alex/AT
25.03.2005, 09:50
Вот, выношу на обсуждение кое-какие предложения по поводу организации памяти и принципа работы с ней в ОС. С 64к много не накрутишься, но такая архитектура кажется мне достаточно приемлемой... Кодом пока не подтверждено.

Менеджер памяти ОС.

* Структура памяти.

Вся системная память делится ОС на 4К-страницы. Существует память
ROM, OS RAM, общая память, заменяемая память (область подкачки) и
память приложений. Адресное пространство системы состоит из 16 таких
страниц. Приложения могут динамически распределить любое число 4К
страниц, но не менее чем 4К. Если необходимо более точное выделение
памяти, приложение должно использовать процедуры менеджера системной
(не страничной) памяти.

Память ROM - это страницы, содержащие системное ПЗУ. Они используются
только тогда, когда HAL делает системные вызовы. В этих страницах
также находится ПЗУ TR-DOS (или ZXDOS).

Память OS RAM - это страницы с кодом и данными ОС. Эти страницы
являются системными и не должны изменяться приложениями. Эти
страницы не заменяемы (сама ОС может заменять их и разделять их со
страницами ROM), но это прозрачно для приложений.

Общая память - это страницы, память в которых может быть распределена
приложениям и сохраняет свой адрес независимо от текущего приложения.
Это весьма ограниченный ресурс, который должен использоваться крайне
аккуратно. Приложения могут использовать его для коммуникации между
процессами/страницами, однако объем памяти, распределяемый в этой
области, ограничен для каждого приложения. Блоки памяти в этой области
именованы (имеют символьные идентификаторы).

Заменяемая память (область подкачки) используется приложениями для
обращения к статической/динамической распределяемой памяти.

Страницы памяти приложений содержат код и статические данные
приложений. Эта область обычно имеет гранулярность системных страниц
(не менее 16 кб), однако одна системная страница может содержать более
одного приложения. Если приложению нужно более одной 16К-страницы,
то приложение должно использовать систему межстраничных вызовов.
Межстраничные вызовы занимают 5 байт памяти и настраиваются ОС во
время загрузки приложения.

* Статическая и динамическая память.

Когда приложению необходимо поместить некоторую страницу RAM (4 кб) в
некоторую физическую страницу, вызов ОС возвращает адрес физической
страницы в регистре IY. Эта память будет доступна до следующего
вызова размещения страницы в той же физической странице. Такая
память называется динамической. Любой вызов к функциям размещения
динамической памяти делает невозможным доступ к статической памяти до
следующего вызова размещения страниц статической памяти.

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

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

* Менеджер системной памяти.

Если приложению не требуются полные 4К-страницы, оно может использовать
менеджер системной памяти. Менеджер системной памяти распределяет
4К-страницы по мере необходимости, выделяя приложениям требуемый объем.
Менеджер системной памяти предоставляет собственные функции размещения
памяти в страницах, аналогичные функциям размещения динамической памяти.
Для своей работы менеджер памяти создает в 4К-страницах собственные
структуры. Память, распределяемая через менеджер памяти, выравнивается
по 256-байтным границам.

* Карта памяти

** Вариант 1: стандартная конфигурация

#0000 #3FFF ROM (4)
#4000 #5FFF Экран, общая память (2)
#6000 #9FFF OS RAM (4)
#A000 #BFFF Область подкачки (2)
#C000 #FFFF Область приложений (4)

** Вариант 2: размещаемая ROM (cache и др.)

#0000 #3FFF ROM, OS RAM, ZXDOS (4)
#4000 #6FFF Экран, общая память (3)
#7000 #8FFF OS RAM (2)
#9000 #BFFF Область подкачки (3)
#C000 #FFFF Область приложений (4)

** Вариант 3: возможность замены страницы #8000-#BFFF

#0000 #3FFF ROM (4)
#4000 #5FFF Экран, общая память (2)
#6000 #7FFF OS RAM (2)
#8000 #BFFF OS RAM, область подкачки (4)
#C000 #FFFF Область приложений (4)

** Вариант 4: размещаемая ROM, возможность замены страницы #8000-#BFFF

#0000 #3FFF ROM, OS RAM, ZXDOS (4)
#4000 #6FFF Экран, общая память (3)
#7000 #7FFF OS RAM (1)
#8000 #BFFF OS RAM, область подкачки (4)
#C000 #FFFF Область приложений (4)

Vitamin
25.03.2005, 19:54
А вообще, ваш метод годится при наличии небольшой доп. утилиты для любого компилятора, хоть даже и с паскаля или С. Достаточно откомпилировать с 2х разных адресов, и подать на вход такой утилиты, и совсем несложно выявить слова, которые отличаются на смещение, которое должно наблюдаться (start2-start1).
наверное, ты совсем понял задачи, возлагаемые на модульную библиотеку. это не только создание релоцируемого кода, а возможности для связки модулей и реализации апи. потому как кроме вызовов функций и переходов еще есть переменные, константы. и они могут меняться и зависеть от других частей системы.

Vitamin
25.03.2005, 20:09
Блоки памяти в этой области
именованы (имеют символьные идентификаторы).

зачем? по крайней мере, почему символьные?

прочитал идеи, с чем-то согласен, с чем-то нет.

вот что было наработано в результате бурных дискуссий с GriV'ом (лог аськи занимает около 50кб %) )
-память в диапазоне от #5c00-#bfff является системной и система распоряжается ею по своему усмотрению. часть области занята статическими таблицами, остальная часть распределяется через менеджер кучи и работа идет с точностью до байта
-последние 16кб адресного пространства предназначены для хранения приложений и их данных. распределяется с помощью менеджера верхней памяти (на системном уровне) и выделяется блоками по 256 байт. менеджер поддерживает множественное владение блоками памяти. для процессов верхняя память выделяется побайтово, все преобразования на себя берет менеджер памяти.
-все необходимые системе данные (процессы, файлы и т.д) хранятся дескрипторами в виде связных списков в нижней памяти.
-процессы могут иметь локальную область памяти, управляемую системным менеджером для работы с "кучей" (heap) на уровне байтов.

GriV
26.03.2005, 11:07
Даже и добавить нечего..

А если честно, я очень рад что идёт здоровая критика... :)
И очень рад что люди включаются в осуждение этой темы... :)

Alex/AT
27.03.2005, 09:53
У меня примерно то же самое. Основное в написанном - идея "свопинга". Ибо когда процессу отведено только 16 кб, переключить их и оставить целым процесс будет проблематично. Значит надо делать место для "свопинга". А для IPC - та самая "общая память".


зачем? по крайней мере, почему символьные?
А потому что там все равно область IPC, и найти память процесса по символьному ID гораздо легче чем по некому абстрактному числу. Кроме того, никто не мешает использовать числовые ID, просто они будут на байт больше ;)

Vitamin
27.03.2005, 22:47
А потому что там все равно область IPC, и найти память процесса по символьному ID гораздо легче чем по некому абстрактному числу. Кроме того, никто не мешает использовать числовые ID, просто они будут на байт больше ;)
с каких это пор поиск по символьной строке быстрее и проще чем поиск по числовому идентификатору? а еще существует бинарный поиск или индексирование. или ты придумал алгоритм хеширования, который не требует траты времени ;)

SfS
28.03.2005, 09:30
Вижу выросла отдельная тема ! ) Классно! Я не имел доступа к инету и накидал спецификацию на память. Кидаю ее сюда юез исходников на С (они пока в отладке).

Почитайте и поругайте. Потом посмотрим как и что улучшить.

Alex/AT
28.03.2005, 23:07
Я не о том. Помнить, какой процесс какие числовые идентификаторы имеет - трудно, не находишь? Да и пересекаться они могут. Я уже не о простоте бинарного поиска ;)

Vitamin
29.03.2005, 20:42
Я не о том. Помнить, какой процесс какие числовые идентификаторы имеет - трудно, не находишь? Да и пересекаться они могут. Я уже не о простоте бинарного поиска ;)
вся верхняя память (как для кода так и для данных) описывается в виде дескрипторов. причем у одного куска памяти может быть несколько дескрипторов, если он (кусок памяти) принадлежит нескольким процессам. а дескрипторы хранятся в виде связного списка. пробежать по списку и найти все дескрипторы с нужным владельцем- не проблема.

GriV
29.03.2005, 23:45
здесь схема вызовов, критикуйте.

SfS
31.03.2005, 06:23
Прочитал. В своей спецификации я про подкачку и не подумал. Но ее можно без особого труда туда ввести. Например для каждой страницы вводим флаг - fPAGEINSWAP - если он установлен - страница в файле подкачки. если сброшен - в памяти. Только тогда придется полностью виртуализовать номера страниц, хотя там это уже сделано для компов в которых не всеми окнами памяти можно щелкать.

Если хочешь - прочитай мой набросок спецификации в этой теме, сравни со своими идеями и подумай нельзя ли их както совместить. Я пока подумаю про реализацию подкачки в рамках своей спецификации.

GriV
07.04.2005, 20:12
так что не вижу причин враждовать %)

То, что ты привёл в описание имеет практическую реализацию?

SfS
19.05.2005, 07:24
так что не вижу причин враждовать %)

То, что ты привёл в описание имеет практическую реализацию?

Враждовать причин нет - это точно. Реализация пока "в процессе". Но в связи с покупкой жилья пока нет времени до ума довести. Скорее всего время появится только через месяц-два.
Но форум смотрю постоянно - если возникнут мысли - кидай. Обсудим по мере возможности.

SfS
18.07.2005, 13:14
Отдаю менеджер локальной кучи безвозмездно и бескорыстно. Особенности:
-автоматом определяет размерность адреса
-функция l_free имеет защиту от дурака (неправильный указатель).
Работает не очень быстро, но надежно. Проверен необнократно на AVR (16битный адрес) и ARM (32битный адрес). Под z80 компилил при помощи SDCC. Вроде тоже нормально работает.

Текст состоит из двух файлов. heap.h и heap.c.
Вопросы задавать МОЖНО :)

SfS
18.07.2005, 13:18
//----------------------------------------------------------------------------
// Работа с кучей. Заголовочный файл heap.h
// SfS (c)2005
// Структура кучи:
// |t_dynamem_descr| memory part |t_dynamem_descr| memory part |...|t_dynamem_descr| memory part |
// где
// t_dynamem_descr - описатель элемента памяти (см. ниже)
// memory part - элемент памяти, который описывается соответствующим описателем
//
// Поле next_dynamem_descr описателя последнего элемента памяти всегда указывает
// на описатель первого элемента (циклическая структура структура).
// Поле prev_dynamem_descr описателя первого элемента памяти указывает
// на первый элемент (сам на себя).
//----------------------------------------------------------------------------
#ifndef heap_h
#define heap_h
//----------------------------------------------------------------------------
// Макрос HEAP_ALIGN задает нужно ли выравнивание.
// Общий вид:
// #define HEAP_ALIGN n /* n=0,2,4,8... */
// Выравнивание производится только по границе 2^n байт
// #define HEAP_ALIGN 0x00 /* нет выравнивания */
// #define HEAP_ALIGN 0x02 /* выравнивание по полуслову (16 бит) */
// #define HEAP_ALIGN 0x04 /* выравнивание по слову (32 бит) */
// #define HEAP_ALIGN 0x08 /* выравнивание по двойному слову (64 бит) */
// Если используется выравнивание, то начало кучи и ее размер так же должны быть
// выровнены !
//----------------------------------------------------------------------------
#ifndef HEAP_ALIGN
#define HEAP_ALIGN sizeof(int)
#else
#if HEAP_ALIGN==0
#undef HEAP_ALIGN
#endif /* 0 */
#endif /* HEAP_ALIGN */
//----------------------------------------------------------------------------
// Флаги (поле flags типа t_dynamem_descr)
#define DM_BUSY 0x00000001 /* Если флаг равен 1, то блок памяти занят */
//----------------------------------------------------------------------------
// Описатель элемента кучи (тип-структура t_heap_el)
//----------------------------------------------------------------------------
typedef struct
{void* next_dynamem_descr; // Указатель на описатель следующего элемента
// Поле next_dynamem_descr последнего элемента
// памяти всегда указывает на
// описатель первого элемента
// (циклическая структура структура).
//
void* prev_dynamem_descr; // Указатель на описатель предыдущего элемента
// Для первого элемента кучи этот указатель указывает
// на первый элемент (сам на себя).
//
void* this_memory_part; // Указатель на элемент памяти. Элемент ВСЕГДА
// расположен сразу за описателем
unsigned int this_memory_size; // Размер элемента памяти (в байтах)
unsigned int flags; // Флаги элемента памяти
}t_heap_el;
//----------------------------------------------------------------------------
// Структура-описатель кучи (тип-структура t_heap)
//----------------------------------------------------------------------------
typedef struct
{// Указатель на текущий элемент кучи
t_heap_el* curel;
// Указатель на начало кучи (на описатель первого элемента)
t_heap_el* firstel;
}t_heap;
//----------------------------------------------------------------------------
// На всякий случай :)
//----------------------------------------------------------------------------
#ifndef NULL
#define NULL 0
#endif
//----------------------------------------------------------------------------
// Резервирует память в куче 'heap', размером 'size' байт.
// Возвращает указатель на зарезервированную память
// Если в куче не достаточно памяти, то возвращает NULL
//----------------------------------------------------------------------------
void* l_malloc(t_heap* heap, unsigned int size);
//----------------------------------------------------------------------------
// Резервирует память в куче 'heap', для размещения массива из 'nmemb' элементов,
// каждый из которых имеет размер 'size'
// Возвращает указатель на зарезервированную память
// Если в куче не достаточно памяти, то возвращает NULL
//----------------------------------------------------------------------------
void* l_calloc(t_heap* heap,int nmemb,int size);
//----------------------------------------------------------------------------
// Освобождает зарезервированную память, на которую указывает 'ptr' в куче 'heap'.
// Если указатель указывает на память, которая не была выделена ранее
// функциями l_malloc() или l_calloc() или указатель 'ptr' равен 0, то ничего не
// происходит
void l_free(t_heap* heap, void* ptr);
//----------------------------------------------------------------------------
// Инициализирует кучу 'heap'.
// heap_begin - адрес начала кучи
// heap_size - размер кучи (включая все описатели)
// Куча занимает в памяти адресное пространство
// от heap_begin до (heap_begin+heap_size-1)
void l_heapinit(t_heap* heap, void* heap_begin, int heap_size);
//----------------------------------------------------------------------------
#endif /* heap_h */

SfS
18.07.2005, 13:19
//----------------------------------------------------------------------------
// Работа с кучей. Заголовочный файл.
// Файл heap.h
// Структура кучи:
// |t_dynamem_descr| memory part |t_dynamem_descr| memory part |...|t_dynamem_descr| memory part |
// где
// t_dynamem_descr - описатель элемента памяти (см. ниже)
// memory part - элемент памяти, который описывается соответствующим описателем
//
// Поле next_dynamem_descr описателя последнего элемента памяти всегда указывает
// на описатель первого элемента (циклическая структура структура).
// Поле prev_dynamem_descr описателя первого элемента памяти указывает
// на первый элемент (сам на себя).
//----------------------------------------------------------------------------
#ifndef heap_h
#define heap_h
//----------------------------------------------------------------------------
// Макрос HEAP_ALIGN задает нужно ли выравнивание.
// Общий вид:
// #define HEAP_ALIGN n /* n=0,2,4,8... */
// Выравнивание производится только по границе 2^n байт
// #define HEAP_ALIGN 0x00 /* нет выравнивания */
// #define HEAP_ALIGN 0x02 /* выравнивание по полуслову (16 бит) */
// #define HEAP_ALIGN 0x04 /* выравнивание по слову (32 бит) */
// #define HEAP_ALIGN 0x08 /* выравнивание по двойному слову (64 бит) */
// Если используется выравнивание, то начало кучи и ее размер так же должны быть
// выровнены !
//----------------------------------------------------------------------------
#ifndef HEAP_ALIGN
#define HEAP_ALIGN sizeof(int)
#else
#if HEAP_ALIGN==0
#undef HEAP_ALIGN
#endif /* 0 */
#endif /* HEAP_ALIGN */
//----------------------------------------------------------------------------
// Флаги (поле flags типа t_dynamem_descr)
#define DM_BUSY 0x00000001 /* Если флаг равен 1, то блок памяти занят */
//----------------------------------------------------------------------------
// Описатель элемента кучи (тип-структура t_heap_el)
//----------------------------------------------------------------------------
typedef struct
{void* next_dynamem_descr; // Указатель на описатель следующего элемента
// Поле next_dynamem_descr последнего элемента
// памяти всегда указывает на
// описатель первого элемента
// (циклическая структура структура).
//
void* prev_dynamem_descr; // Указатель на описатель предыдущего элемента
// Для первого элемента кучи этот указатель указывает
// на первый элемент (сам на себя).
//
void* this_memory_part; // Указатель на элемент памяти. Элемент ВСЕГДА
// расположен сразу за описателем
unsigned int this_memory_size; // Размер элемента памяти (в байтах)
unsigned int flags; // Флаги элемента памяти
}t_heap_el;
//----------------------------------------------------------------------------
// Структура-описатель кучи (тип-структура t_heap)
//----------------------------------------------------------------------------
typedef struct
{// Указатель на текущий элемент кучи
t_heap_el* curel;
// Указатель на начало кучи (на описатель первого элемента)
t_heap_el* firstel;
}t_heap;
//----------------------------------------------------------------------------
// На всякий случай :)
//----------------------------------------------------------------------------
#ifndef NULL
#define NULL 0
#endif
//----------------------------------------------------------------------------
// Резервирует память в куче 'heap', размером 'size' байт.
// Возвращает указатель на зарезервированную память
// Если в куче не достаточно памяти, то возвращает NULL
//----------------------------------------------------------------------------
void* l_malloc(t_heap* heap, unsigned int size);
//----------------------------------------------------------------------------
// Резервирует память в куче 'heap', для размещения массива из 'nmemb' элементов,
// каждый из которых имеет размер 'size'
// Возвращает указатель на зарезервированную память
// Если в куче не достаточно памяти, то возвращает NULL
//----------------------------------------------------------------------------
void* l_calloc(t_heap* heap,int nmemb,int size);
//----------------------------------------------------------------------------
// Освобождает зарезервированную память, на которую указывает 'ptr' в куче 'heap'.
// Если указатель указывает на память, которая не была выделена ранее
// функциями l_malloc() или l_calloc() или указатель 'ptr' равен 0, то ничего не
// происходит
void l_free(t_heap* heap, void* ptr);
//----------------------------------------------------------------------------
// Инициализирует кучу 'heap'.
// heap_begin - адрес начала кучи
// heap_size - размер кучи (включая все описатели)
// Куча занимает в памяти адресное пространство
// от heap_begin до (heap_begin+heap_size-1)
void l_heapinit(t_heap* heap, void* heap_begin, int heap_size);
//----------------------------------------------------------------------------
#endif /* heap_h */

SfS
18.07.2005, 13:34
Сильно длинный файл.
Архив исходников тут

http://www.nedopc.org/nedopc/upload/heap.tar

Комментарии на русском в кодировке koi8-r