НОВОЕ: OS/2 GURU - Вопросы и ответы

Reviews / articles about OS/2

Operating systems:
ArcaOS, eComStation, IBM OS/2 Warp
Мифы о eComStation 

Unsorted

 

 

ArcaOS 5.0 Русская версия
Пакет русификации ArcaOS 5.0 OS/2 давно доступен. Поддерживается любая версия: 5.0, 5.0.1, 5.0.2.

eCo Software может выпустить и другие пакеты (Немецкий, Голландский, Бразильский Португальский, Испанский, Шведский и т.д.)

Использование High Memory в OS/2


TITLE: Использование High Memory в OS/2

DATE: 2003-07-07 11:13:31

AUTHOR: Evgeny Kotsuba

Предисловие

Дмитpий Завалишин(dz) в свое время (в 1997 году) писал в статье "Пpогpаммиpование в OS/2" [1]

Проблема, вызванная отсутствием проблем с памятью

Один из вечных вопросов программирования в DOS - память, а точнее ее нехватка. Вечные свопинги на диск перед запуском другой программы, копание во временных файлах, работа с данными по маленьким кусочкам - все это отнимает массу времени и заставляет идти на ухищрения при решении, казалось бы, элементарных программистских задач. При программировании в операционных средах с виртуальной памятью (а к таковым принадлежит и OS/2) все это совершенно не имеет смысла до тех пор, пока размеры данных не приближаются к пределу адресного пространства процессов (для текущей версии OS/2 - около 512 мегабайтов, в будущих версиях предполагается увеличение). Забудьте про временные файлы, и пусть ничто не остановит вас перед, скажем, такой последовательностью операторов:

int fd = open (filename,mode);
long size_of_file = filesize(fd);
char * input = malloc(size_of_file);
read(fd, input, (unsigned) size_of_file);

(Не забудьте, что это - 32-битовый мир, и параметр sizeof третьего аргумента функции read равен 4, как и параметр long)

т.е. вроде бы как не стоит писать

char * input = malloc(size_of_file); if(input == NULL)
{ printf("Error: malloc кердык\n");
exit(1);
}

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

Проблема

И вот наступает это самое светлое завтра, когда "размеры данных приближаются к пределу адресного пространства процессов". И в один прекрасный момент ваша программа загибается неизвестно где и почему. При разборе полетов выясняется, что malloc таки возвращает вам NULL. После чего вы решаете таки проверять возвращаемое maloc/realloc значение и ставите вышеописанную конструкцию. Однако при полевых испытаниях программа опять иногда загибается без сообщения об ошибке.

Делаем тестовую программу memEat.cpp

Программа выделяет по 1 Мб памяти, после получения NULL от malloc выводит сообщение об ошибке и выделяет по 1k , после очередного NULL выводит второе сообщение об ошибке и выделяет по 32 байта, после получения третьего NULL выводит третье сообщение и выходит по exit(1).

RAM eat:246Mb
RAM eat:247Mb
RAM eat:248Mb
Error1: malloc кердык
15675:263.3086Mb:
Error2: malloc кердык
5216:263.467804Mb:

Позвольте, а где обещанные 512 Mb ?! А где последнее сообщение об ошибке ?!

С последним сообщением все более-менее понятно: printf & Co сами пользуются внутри malloc'ом и в условиях нехватки памяти не работают. В случае использования более сложной системы обработки сообщений об ошибках (например, для PM-программы) вероятность попасть на функцию, использующую функцию, вызывающую malloc/calloc/realloc, значительно увеличивается.

А что же с нашими 512Mb? Вспоминаем разные умные статьи, доки и факи. Пропустим процесс вспоминания, представим сразу результаты.

Системная информация

DosQuerySysInfo предоставляет нам достаточно много интересных параметров, среди которых QSV_MAXPRMEM и QSV_MAXSHMEM.

Попробуем собрать всю возможную информацию о системе, которую предоставляет фунция DosQuerySysInfo. При этом обнаруживается, что параметр QSV_MAX вообще говоря разный для разных версий оси. (от 27 до 32). Собираем тестовую программу DosQuery.cpp и смотрим что получается:

19 Maximum number of bytes of memory that can be allocated by all processes in the system 529948672

20 Maximum number of bytes of memory that this process can allocate in its private arena. 318111744

21 Maximum number of bytes of memory that a process can allocate in the shared arena. 193331200

19 Maximum number of bytes of memory that can be allocated by all processes in the system 2068279296

20 Maximum number of bytes of memory that this process can allocate in its private arena. 271450112

21 Maximum number of bytes of memory that a process can allocate in the shared arena. 231538688

Revision 14.088 Revision 9.032

Таким образом мы можем сообразить, что реально malloc/calloc, который внутри вызывает DosAllocMem, может использовать памяти не более, чем указано в QSV_MAXPRMEM(20). Почему и откуда получается цифра в QSV_MAXSHMEM(21) - это на сегодняшний день науке неизвестно, но, очевидно, что чем больше значение для shared arena, тем меньше обычной памяти остается для процесса. Попутно можем заметить "драматическую разницу" в значениях для QSV_TOTAVAILMEM (19) для разных версий OS/2. Кроме того, заметим, что разница DosQuerySysInfo при реакции на запрос QSV_MAX параметров может быть использована нами для определения наличия поддержки High Memory, хотя далее мы будем использовать более прямой метод.

VIRTUALADDRESSLIMIT

VIRTUALADDRESSLIMIT - такая строка в CONFIG.SYS появилась в авроровских ядрах, вот что по этому поводу написано в [5]

A new config.sys parameter, VIRTUALADDRESSLIMIT, has been added to specify the highest virtual memory address that may be accessed. The default value for OS/2 Warp Server for SMP is 2 GB, which is specified in megabytes in the command argument:

VIRTUALADDRESSLIMIT=2048

The smallest value allowed is 512, which provides compatability with previous versions of OS/2 which only allowed access to 512 MB. The largest value supported is 3072 (3 GB). The VIRTUALADDRESSLIMIT parameter may be specified as part of selective install.

Values in excess of 512 will reduce the number of processes that can concurrently run on the system. When memory has been exhausted, OS/2 control program functions will fail with ERROR_NOT_ENOUGH_MEMORY and PM functions will fail with PMERR_INSUFFICIENT_MEMORY.

This information can be found in the SMP programming addendum, shipped as part of the Warp SMP toolkit (SMP.INF).

Warp SMP toolkit я не видел, а в обычном тулките есть

addendum.inf | 232941 |16/10/00 | 13:14

Однако в этом addendum.inf нет никаких подробностей о том как все это можно использовать. Есть только суперлаконичный список APIs Supporting High Memory Objects.

Запускаем DosQuery на машине с Revision 14.088 и параметром VIRTUALADDRESSLIMIT= 3072 в CONFIG.SYS

26 Number of processors in the machine 1

27 Maximum amount of free space in process's high private arena 2348810240

28 Maximum amount of free space in process's high shared arena 2348810240

29 Maximum number of concurrent processes supported 172

30 Size of the user's address space in megabytes 3072

Итак, на новых ядрах процессу доступно раз в десять больше памяти, как же ее теперь можно использовать?

OBJ_ANY: секретный параметр

Точно помню, что видел описание того, как можно использовать High Memory, вот где - так и не вспомнил. Единственное, что находит Google - это [7]

(To activate HMS in your application, use the attribute OBJ_ANY with the memory allocation functions!)

APIRET result;
PCHAR lowp = NULL;
ULONG i, size;

size = 1024*1024;
result = DosAllocMem((PPVOID)&lowp,size,
PAG_COMMIT | PAG_WRITE | OBJ_ANY)

\TOOLKIT\H\bsememf.h содержит следующее:

#define OBJ_ANY 0x00000400U /* allocate memory anywhere */

В случае использования параметра OBJ_ANY на старых ядрах DosAllocMem возращает ERROR_INVALID_PARAMETER (87). Этот факт можно использовать для тестирования возможности работы с High Memory.
Итак, мы вспомнили или научились выделять память из High Memory private arena. Осталось научится пользоваться этим по-человечески.

User's heap

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

Например:

void * _mesa_malloc(size_t bytes)
{
#if defined(XFree86LOADER) && defined(IN_MODULE)
return xf86malloc(bytes);
#else
return malloc(bytes);
#endif
}

Таким образом можно сосредоточить все функции для работы с памятью в одном месте, а затем легким движением руки заменить вызов malloc на что-то другое. Далее обращаемся к документации Visual Age C++, раздел Managing Memory With Multiple Heaps:

VisualAge C++ now gives you the option of creating and using your own pools of memory, called heaps. You can use your own heaps in place of or in addition to the default VisualAge C++ runtime heap to improve the performance of your program. This section describes how to implement multiple user-created heaps using VisualAge C++.

Note: Many readers will not be interested in creating their own heaps. Using your own heaps is entirely optional, and your applications will work perfectly well using the default memory management provided (and used by) the VisualAge C++ runtime library. If you want to improve the performance and memory management of your program, multiple heaps can help you. Otherwise, you can ignore this section and any heap-specific library functions.

Работу с пользовательскими кучами поддерживают функции из семейства _umalloc и описаны они в . EMX тоже содержит ([9]). Простейший пример работы с _umalloc смотрите в [8]. А теперь - внимание! Делаем простейшее мыслительное усилие. Читаем описание _ucreate, смотрим в пример работы и догадывамся добавить в вызов DosAllocMem секретный параметр OBJ_ANY

#include
Heap_t _ucreate(void *block, size_t initsz, int clean, int memtype,
void *(*getmore_fn)(Heap_t, size_t *, int *)
void (*release_fn)(Heap_t, void *, size_t);

Language Level: Extension

_ucreate creates your own memory heap that you can allocate and free memory from, just like the VisualAge C++ runtime heap.

Before you call _ucreate, you must first get the initial block of memory for the heap. You can get this block by calling an OS/2 API (such as DosAllocMem or or DosAllocSharedMem) or by statically allocating it. (See the CP Programming Guide and Reference for more information on the OS/2 APIs.)
Note: You must also return this initial block of memory to the system after you destroy the heap.

When you call _ucreate, you pass it the following parameters:

  • block The pointer to the initial block you obtained.
  • initsz The size of the initial block, which must be at least _HEAP_MIN_SIZE bytes (defined in). If you are creating a fixed-size heap, the size must be large enough to satisfy all memory requests your program will make of it.
  • clean The macro _BLOCK_CLEAN, if the memory in the block has been initialized to 0, or !_BLOCK_CLEAN, if the memory has not been touched. This improves the efficiency of _ucalloc; if the memory is already initialized to 0, _ucalloc does not need to initialize it.
    • Note: DosAllocMem initializes memory to 0 for you. You can also use memset to initialize the memory; however, memset also commits all the memory at once, an action that could slow overall performance.
  • memtype A macro indicating the type of memory in your heap: _HEAP_REGULAR (regular) or _HEAP_TILED (tiled). If you want the memory to be shared, specify _HEAP_SHARED as well (for example, _HEAP_REGULAR | _HEAP_SHARED). Tiled memory blocks will not cross 64K boundaries, so the data in them can be used in 16-bit programs. Shared memory can be shared between different processes. For more information on different types of memory, see the Programming Guide and the Control Program Guide and Reference.
    • Note: Make sure that when you get the initial block, you request the same type of memory that you specify for memtype.
  • getmore_fn A function you provide to get more memory from the system (typically using OS/2 APIs or static allocation). To create a fixed-size heap, specify NULL for this parameter.
  • release_fn A function you provide to return memory to the system (typically using DosFreeMem). To create a fixed-size heap, specify NULL for this parameter.

If you create a fixed-size heap, the initial block of memory must be large enough to satisfy all allocation requests made to it. Once the block is fully allocated, further allocation requests to the heap will fail. If you create an expandable heap, the getmore_fn and release_fn allow your heap to expand and shrink dynamically.

When you call _umalloc (or a similar function) for your heap, if not enough memory is available in the block, it calls the getmore_fn you provide. Your getmore_fn then gets more memory from the system and adds it to the heap, using any method you choose.

Your getmore_fn must have the following prototype:

void *(*getmore_fn)(Heap_t uh, size_t *size, int *clean);

where:

  • uh Is the heap to get memory for.
  • size Is the size of the allocation request passed by _umalloc. You probably want to return enough memory at a time to satisfy several allocations; otherwise, every subsequent allocation has to call getmore_fn. You should return multiples of 64K (the smallest size that DosAllocMem returns). Make sure you update the size parameter if you return more than the original request.
  • clean Within getmore_fn, you must set this variable either to _BLOCK_CLEAN, to indicate that you initialized the memory to 0, or to !_BLOCK_CLEAN, to indicate that the memory is untouched.

Note: Make sure your getmore_fn allocates the right type of memory for the heap.

When you call _uheapmin to coalesce the heap or _udestroy to destroy it, these functions call the release_fn you provide to return the memory to the system. function.

Your release_fn must have the following prototype:

void (*release_fn)(Heap_t uh, void *block, size_t size);

The heap uh the block is from, the block to be returned, and its size are passed to release_fn by _uheapmin or _udestroy.

For more information about creating and using heaps, see the "Managing Memory" in the Programming Guide.

Return Value: If successful, _ucreate returns a pointer to the heap created. If errors occur, _ucreate returns NULL.

Таким образом с мы с одной стороны, можем использовать все свои старые функции, которые вызывали обертку для malloc, с другой стороны, остается вся фунциональность , в том числе возможность сжатия кучи и автоматического (в VAC'е) использования отладочных версиий функций этого семейства, с третьей стороны, realloc и free сами умеют распознавать, из какой кучи используется память и их вызовы ничем заменять не нужно. Еще с одной стороны - в программе остается обычная куча, которую используют функции printf и другие и таким образом, в случае некорректной работы с нашей пользовательской кучей есть надежда, что по-крайней мере сообщения об ошибках будут будут доставлены по назначению.

Собственно, и это все ;-). Осталось только реализовать.

В примере Mem_OBJ.cpp: выделяется(но не коммитится) 1 Гб памяти, потом выделяется еще 1 Мб , и пишется на диск посредством fwrite c передачей ему указателя из High Memory и затем читается и сравнивается с образцом.

В Memory2.cpp и imports.cpp приведен пример pеализации работы с High Memory для порта в OS/2 Mesa3d.

В этой реализации тестировуется возможность работы с High Memory - если возможность есть, то используется _umalloc и DosAllocMem с параметром OBJ_ANY, в противном случае - используется стандартный malloc.

Заключение

Мы научились работать High Memory с минимальными затратами.

При наличии энтузиазма и исходников stdlib можно бы было исправить стандартую библиотеку для работы с High Memory. Для EMX и OpenWatcom'а это вполне возможно.

PS. При большом желании можно работать и с памятью более 4GB на 32битных архитектурах. Для этого надо иметь исходники malloc & Co и добавить функции для обеспечения возможности свопирования объектов памяти в файлы.


Ссылки по теме:

  1. Введение в пpогpаммиpование под OS/2. Дм.Завалишин
  2. OS/2 изнутри: работа с памятью Николай Смирнов, Евгений Лызенко, Мир ПК, #07/1997
  3. The Life Cycle of a Memory Page in OS/2
  4. The OS/2 API Project - DosAllocMem
  5. Frequently Asked Questions OS/2 Warp What is the parameter VIRTUALADDRESSLIMIT in config.sys
  6. os2ddprog/message/2465 -- Scott Garfinkle сознается, что для VMLock есть ключик

    /* Like OBJ_ANY for DosAllocMem -- use
    mem above 512mb line if possible */
    #define VMDHA_USEHMA 0x2000

  7. OS/2 address range и Пример использования OBJ_ANY от Frank Meilinger
  8. Example of Using VisualAge Extenstions to Memory Management Functions
  9. Multiple heaps in EMX -- The interface to multiple heaps is more or less compatible to the one of IBM's Visual Age C++ compiler. Definitions and declarations for using multiple heaps are in the header.
  10. Исходные тексты и три откомпилированнных примера к данной статье в одном HighMemory_examples.zip

Попробуй программу:

Как послушать mp3, flac, ogg, monkey? PM123 - универсальный аудио-проигрыватель.

Комментарии:

Sergey Posokhov
2003-07-07 15:19:16

Странно, почему IBM-ры не решились переписать вызов DosAllocMem раз и навсегда, чтобы он выделял столько памяти, сколько хочет программа. Зачем они сделали к нему дополнительный ключ? Какой в этом смысл?

LightElf
2003-07-07 16:24:23

Вот будет забавно, ежели старой проге, в упор не знающей о High Memory вернут кусок памяти где-нибудь в конце третьего гигабайта... А она этот кусок скормит какому-нибудь 16-битному API. Смеху то будет...

Evgeny
2003-07-07 18:41:30

To:LightElf а ты где пропадаешь ?

Slavik Gnatenko
2003-07-07 20:29:46

"Почему и откуда получается цифра в QSV_MAXSHMEM(21) - это на сегодняшний день науке неизвестно". Вполне известно. На shared memory в первом полугиге отведено 256M пространства. За вычетом уже занятого получается то, что есть. Вообще все эти вещи лучше в Theseus смотреть. Там по линейному пространству есть специальный отчет. В хелпе к нему же есть много чего интересного почитать.

2LightElf: собсно сама по себе 16ти битность не особо страшна. Если делать через хелперы, то все будет работать. Проблема в том, что до недавнего времени для перекодирования 32->16 применялся исключительно tiling. А он есессно ограничен 512M.

Evgen
2003-07-08 01:54:30

To: Slavik Gnatenko: где написано что отведено 256M ? все что я нарыл, вспоминая как оно и где - это что shared mem занимает (от)64Mб .

В тезиусе нифига не понятно. написано что Total free space in shared arena = 86Mб Куды съелись остальные ?

Slavik Gnatenko
2003-07-08 17:14:02

2 Evgen. Где прочитал уже не скажу. Может в debugging handbook.... Тезеуса под рукой сейчас нет, навскидку не помню к чему там эта цифра относится. В общем ситуация такая: есть shared memory watermark. Отмечает ее начало. Изначально стоит на (512-64)M. Есть private memory watermark. Отмечает конец приватного адресного пространства. Изначально стоит на 64M. По мере отведения памяти они сдвигаются навстречу друг другу. Текущую разницу можно посмотреть в тезеусе. Обратно первый не сдвигается, поэтому шареную память нужно пользовать по минимуму. Если освободить блок, то образуется дыра. Вероятно суммарный размер этих дыр и есть 86Mb. Но могу и наврать. Надо отчет смотреть.

Evgeny
2003-07-08 19:48:16

To: Slavik Gnatenko

ну вот мне и непонятно, куда делось еще 200Мб , ведь это ж фиг знает сколько ...И вообще, чего-то мне кажется, что раньше оно так не было...

VicTor Smirnoff
2003-07-12 15:25:28

Ну на размер доступной памяти ещё DLLBASING=OFF влияет.

Doodle
2003-07-14 15:27:39

Any chance to read this article in english in the foreseeable future?

I'm quite interested in it.

...Безнадега
2003-07-15 00:47:06

world.altavista.com will provide you an instant translation

...Безнадега
2003-07-15 00:47:20

world.altavista.com will provide you an instant and quite readable translation

Evgen
2003-07-15 01:37:11

To: Doodle this article is mainly lirics ;-)

in a nutshell you can look at bin and code of samples in

[url]

Doodle
2003-07-17 16:00:43

thnx to all!

Как подключиться к разработке eComStation? Участвуй в проектах eCo Labs

 


 

(C) OS2.GURU 2001-2021