Подтвердить что ты не робот

Создание функции C с заданным размером в текстовом сегменте

Я программирую встроенный powerpc 32 с 32-байтным 8-полосным ассоциативным кэшем команд L2. Чтобы избежать обхода кеша, мы выравниваем функции таким образом, чтобы текст набора функций, называемых на частоте высокой частоты (думаю, код прерывания) заканчивается в отдельных наборах кешей. Мы делаем это, вставляя фиктивные функции по мере необходимости, например.

void high_freq1(void)
{
   ...
}

void dummy(void)
{
   __asm__(/* Silly opcodes to fill ~100 to ~1000 bytes of text segment */);
}

void high_freq2(void)
{
   ...
}

Это выглядит как уродливое и неоптимальное. Что я хотел бы сделать, это

  • полностью избегать __asm__ и использовать чистый C89 (возможно, C99)
  • найдите способ создания нужного разделителя dummy(), который оптимизатор GCC не коснется
  • размер спейсера dummy() должен быть сконфигурирован как кратный 4 байтам. Типичные разделители составляют от 260 до 1000 байтов.
  • должно быть возможным для набора из примерно 50 функций из 500 функций.

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

4b9b3361

Ответ 1

Возможно, скрипты компоновщика. Компилятор GNU может использовать их, я думаю... Я использовал файлы LD для AVR и MQX, оба из которых мы используем компиляторы на основе GCC... может помочь...

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

Попробуйте выполнить поиск файлов команд в стиле SVR3.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: следующий пример для очень специфического компилятора... но SVR3-подобный формат довольно общий... вам нужно будет прочитать свою систему

Например, вы можете использовать команды, например...

ApplicationStart = 0x...;
MemoryBlockSize = 0x...;
ApplicationDataSize  = 0x...;
ApplicationLength    = MemoryBlockSize - ApplicationDataSize;

MEMORY {
    RAM: ORIGIN = 0x...                LENGTH = 1M
    ROM: ORIGIN = ApplicationStart     LENGTH = ApplicationLength   
}

Это определяет три раздела памяти для компоновщика. Тогда вы можете сказать такие вещи, как

SECTIONS
{
    GROUP :
    {       
        .text :
        {
            * (.text)
            * (.init , '.init$*')
            * (.fini , '.fini$*')
        }

        .my_special_text ALIGN(32): 
        {
            * (.my_special_text)
        } 

        .initdat ALIGN(4):
        // Blah blah
    } > ROM
    // SNIP
}

Команда SECTIONS сообщает компоновщику, как отображать разделы ввода в секции вывода и как размещать выходные разделы в памяти... Здесь мы говорим, что происходит в разделе вывода ПЗУ, которое мы определили в определение MEMORY выше. Интересующий вас бит - .my_special_text. В вашем коде вы можете делать такие вещи, как...

__attribute__ ((section(".my_special_text")))
void MySpecialFunction(...)
{
    ....
}

Линкером будет помещена любая функция, которой предшествует оператор __attribute__ в раздел my_special_text. В приведенном выше примере это помещается в ПЗУ на следующей 4-байтовой выровненной границе после раздела text, но вы можете поместить его в любом случае. Таким образом, вы можете сделать несколько разделов, по одному для каждой из описываемых вами функций, и убедитесь, что адреса не будут вызывать столкновения...

Вы можете указать размер и расположение памяти раздела с помощью ссылок, определенных переменными формы

extern char_fsection_name[]; // Set to the address of the start of section_name
extern char_esection_name[]; // Set to the first byte following section_name

Итак, для этого примера...

extern char _fmy_special_text[]; // Set to the address of the start of section_name
extern char _emy_special_text[]; // Set to the first byte following section_name

Ответ 3

Если вы готовы потратить некоторое усилие, вы можете использовать

__attribute__((section(".text.hotpath.a")))

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

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

Пример, предполагая, что вы хотите заблокировать 4KiB в кеше:

SECTIONS {
    .text.hotpath.one BLOCK(0x1000) {
        *(.text.hotpath.a)
        *(.text.hotpath.b)
    }
}
ASSERT(SIZEOF(.text.hotpath.one) <= 0x1000, "Hot Path functions do not fit into 4KiB")

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

Вы даже можете использовать

NOCROSSREFS(.text.hotpath.one .text)

чтобы запретить функции горячего пути, вызывающие другие функции.

Ответ 4

Предполагая, что вы используете GCC и GAS, это может быть простое решение для вас:

void high_freq1(void)
{
   ...
}
asm(".org .+288"); /* Advance location by 288 bytes */
void high_freq2(void)
{
   ...
}

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

Это не чисто C89, конечно, но это может быть менее уродливо, чем использование фиктивных функций.:)

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

EDIT. Как отмечено в комментариях, в этом случае важно передать флаг -fno-toplevel-reorder в GCC.