Jump to content

    
Sign in to follow this  
Darth Vader

Выделение места под стек и кучу в Си

Recommended Posts

Как в Си-файле выделить место под стек и кучу? Компилятор ARM 5.06 (среда Keil MDK ARM 5.24).


Имеется стандартный ассемблерный файл Startup.s из CMSIS.

Spoiler

;<h> Stack Configuration
;  <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;</h>

Stack_Size      EQU      0x00000400

                AREA     STACK, NOINIT, READWRITE, ALIGN=3
__stack_limit
Stack_Mem       SPACE    Stack_Size
__initial_sp


;<h> Heap Configuration
;  <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;</h>

Heap_Size       EQU      0x00000C00

                IF       Heap_Size != 0                      ; Heap is provided
                AREA     HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE    Heap_Size
__heap_limit
                ENDIF

 

Показан фрагмент, где резервируется память под стек и кучу.

Хочу сделать его аналог на Си.
Как объявлять таблицу векторов прерываний много где описано - в любой теме про загрузчики есть эта информация. А вот как выделить (зарезервировать) память под стек и кучу нигде не находил.
Предлагаю здесь обсудить этот вопрос. 
Изначально я хотел сделать так: объявить два массива байт с выравниванием по границе 8 байт: один под стек, второй под кучу.
Размеры задать константами, объявленными, как макросы через #define. Адрес конца массива под стек занести в начало таблицы векторов прерываний.
Встал вопрос: а нужно ли как-то сообщить компилятору/линкеру о размещении и размерах стека и кучи? Оказалось, что да, надо. Здесь пишут, что надо определить имена: __initial_sp, __heap_base, __heap_limit.
Вот по их использованию есть несколько вопросов:
1. В примере по ссылке в ассемблерной вставке эти имена только определяются. Резервирование памяти в явном виде, как в ассемблерном стартапе, не происходит.
Вопрос: почему? Ведь в ассемблерном стартапе память под стек и кучу явно выделяется директивой SPACE.
2. Почему их надо определять именно в ассемблерном коде? Почему нельзя определить в Си-коде через #define? Зачем для этого городить ассемблерные вставки?

Share this post


Link to post
Share on other sites

Как выше было сказано, место резервируется не в стартапе, а в линкере. В стартапе лишь берут данные из линкера и затем инициализируют все необходимое. Я утащил сишный стартап из opencm3. Там прекрасно видно вот это:

extern unsigned _data_loadaddr, _data, _edata, _ebss, _stack;
extern funcp_t __preinit_array_start, __preinit_array_end;
extern funcp_t __init_array_start, __init_array_end;
extern funcp_t __fini_array_start, __fini_array_end;

сами адреса вычисляются линкером из ld-скрипта.

А уж в линкере можно и свои секции объявлять - хоть во флеше, хоть в памяти... Я себе во флеш две секции добавил, чтобы иметь область для сохранения конфигурационных данных и логов.

Share this post


Link to post
Share on other sites
13 minutes ago, aaarrr said:

В "C" можно было только __user_initial_stackheap() использовать.

Так делаю, причем аж в файле *.cpp.

Читать тут: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0099a/armlib_cihhdahf.htm

 

 

Share this post


Link to post
Share on other sites
2 hours ago, Darth Vader said:

 Оказалось, что да, надо. Здесь пишут, что надо определить имена: __initial_sp, __heap_base, __heap_limit.

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

Share this post


Link to post
Share on other sites
17 minutes ago, AlexandrY said:

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

Назначение секций линкером никак не помешает использовать их повторно в "настоящей" куче.

Share this post


Link to post
Share on other sites
8 minutes ago, aaarrr said:

Назначение секций линкером никак не помешает использовать их повторно в "настоящей" куче.

Речь не про то что в принципе помешает или нет, а про более эффективные практики.
По ссылке не менее 3-х методов, но какой наиболее полезный? 

Share this post


Link to post
Share on other sites
2 hours ago, AlexandrY said:

По ссылке не менее 3-х методов, но какой наиболее полезный?

Ну так расскажите, а мы послушаем. Как еще обучиться эффективным практикам-то :lol2:

Share this post


Link to post
Share on other sites

Определить и разместить стек и кучу в С-ном файле несложно. Но у стандартных библиотек есть вызов функции __user_initial_stackheap() в которой стартап должен вернуть в регистрах R0,R1,R2,R3 размер и расположение стека и кучи. Как это сделать без асм я что-то не допёр. Прикладываю мои упражнения по созданию кейловского стартапа на Си.

STM32F411VE-SRAM.sct

startup_stm32f411xe.c

Share this post


Link to post
Share on other sites
1 hour ago, VladislavS said:

Определить и разместить стек и кучу в С-ном файле несложно. Но у стандартных библиотек есть вызов функции __user_initial_stackheap() в которой стартап должен вернуть в регистрах R0,R1,R2,R3 размер и расположение стека и кучи. Как это сделать без асм я что-то не допёр.

using StackItem = uint64_t;
using StackSize = uint32_t;
using HeapItem = uint64_t;

struct InitialStackHeapConfig
{
	unsigned heapBase; 		// low-address end of initial heap
	unsigned stackBase; 	// high-address end of initial stack
	unsigned heapLimit; 	// high-address end of initial heap
	unsigned stackLimit;	// unused
};
#define DEFAULT_HEAP_SIZE_BYTES 		(4096)
#define STARTUP_STACK_SIZE_BYTES 		(512)

__attribute__((used))
__attribute__((section(".stack"))) 
__attribute__((aligned(sizeof(StackItem))))
static StackItem  startupStack[STARTUP_STACK_SIZE_BYTES / sizeof(StackItem)] ;

__attribute__((used))
__attribute__((section(".heap")))
__attribute__((aligned(sizeof(HeapItem))))
static HeapItem defaultHeap[DEFAULT_HEAP_SIZE_BYTES / sizeof(HeapItem)];
extern "C" __attribute__((value_in_regs)) struct InitialStackHeapConfig
__user_setup_stackheap(unsigned R0, unsigned SP, unsigned R2, unsigned SL)
{
   struct InitialStackHeapConfig config;

	config.heapBase = reinterpret_cast<unsigned int>(defaultHeap);
	config.stackBase = reinterpret_cast<unsigned int>(startupStack) + STARTUP_STACK_SIZE_BYTES;
	config.heapLimit = reinterpret_cast<unsigned int>(defaultHeap) + DEFAULT_HEAP_SIZE_BYTES;
	config.stackLimit = reinterpret_cast<unsigned int>(startupStack); // unused

	return config;
}

 

Share this post


Link to post
Share on other sites

Спасибо, будем знать. Вся хитрость в нестандартном 

__attribute__((value_in_regs))

А не подскажешь как надёжно узнать, что тебя из-под Keil компилят, а то эта редиска на __GNUC__ отзывается.

Share this post


Link to post
Share on other sites
2 minutes ago, VladislavS said:

А не подскажешь как надёжно узнать, что тебя из-под Keil компилят, а то эта редиска на __GNUC__ отзывается.

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

 

4 minutes ago, VladislavS said:

Вся хитрость в нестандартном

Это все есть в мануале, оттуда и заимствовано.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this