объединение упакованных данных с согласованным доступом к памяти - PullRequest
6 голосов
/ 02 ноября 2010

Я пытаюсь выполнить оптимизацию памяти, которая должна быть теоретически возможной, но я начинаю сомневаться, что это в пределах возможностей arm-elf-gcc.Пожалуйста, покажите мне, что я не прав.

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

Все это выполняется в физическом адресном пространстве - обычный sramотображается в одном месте, а nvram отображается в другом.Вот в чем проблема - весь доступ к nvram должен быть выполнен в 32-битных словах;доступ к байту или полуслову запрещен (хотя он явно подходит для основной памяти).

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

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

Я надеялся, что __attribute__ ((packed, aligned(4))) или какой-либо его вариант здесь могут помочь, но все чтение и эксперименты, которые я провел до сих пор, подвели меня.

ВотПример такого рода данных конфигурации, с которыми я имею дело:

#define __packed __attribute__ ((packed))
struct __packed Foo
{
    uint64_t foo;
    struct FooFoo foofoo;
}

struct __packed Bar
{
    uint32_t something;
    uint16_t somethingSmaller;
    uint8_t evenSmaller;
}

struct __packed PersistentData
{
    struct Foo;
    struct Bar;
    /* ... */
    struct Baz;
    uint_32 checksum;
}

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

Ответы [ 4 ]

2 голосов
/ 02 ноября 2010

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

Союзы тоже приходят на ум, но я исправлял достаточно много раз, чтобы вы не могли использовать союзы для изменения типов в соответствии со стандартами C.Хотя, как я предполагаю с другим автором, я еще не видел случая, когда использование объединения для изменения типов не работало.Разбитые битовые поля, постоянно нарушенное совместное использование памяти, пока что безболезненно.И профсоюзы не спасут вас ни одного оперативного плеера, поэтому он здесь не работает.

Почему вы пытаетесь заставить компилятор работать?Во время компиляции вам понадобится какой-нибудь сценарий типа компоновщика, который инструктирует компилятору делать 32-битные обращения с масками, сдвигами, чтением-изменением-записью, для некоторых адресных пространств, а для других - более естественным словом, полусловом идоступ к байту.Я не слышал ни о gcc, ни о языке C, имеющем такие элементы управления, будь то в синтаксисе, скрипте компилятора или каком-либо файле определения.И если он существует, он не используется достаточно широко, чтобы быть надежным, я бы ожидал ошибок компилятора и избегал этого.Я просто не вижу, чтобы компилятор делал это, конечно, не в структурной манере.

Для чтения вам может повезти, он сильно зависит от аппаратного обеспечения.Где этот интерфейс памяти nvram, внутри чипа, изготовленного вашей компанией, другой компанией, на краю чипа и т. Д.?Ограничение, подобное тому, которое вы описываете частично, может означать, что управляющие сигналы, которые различают размер доступа или байтовые дорожки, могут игнорироваться.Таким образом, ldrb может выглядеть как nvram как 32-битное чтение, и рука будет захватывать правильную байтовую дорожку, потому что думает, что это 8-битное чтение.Я хотел бы провести несколько экспериментов, чтобы убедиться в этом, так как имеется более одной шины памяти, каждая из которых имеет много разных типов передач.Возможно, поговорите с аппаратными специалистами или проведите некоторые hdl-симуляции, если у вас есть такая возможность, чтобы увидеть, что на самом деле делает рука.Если вы не можете использовать этот ярлык, чтение будет ldr с возможной маской и сдвигом, независимо от того, как вы заставите компилятор это сделать.

Запись, отличная от размера слова, должна выполняться для чтения, изменения и записи.ldr, bic, shift или or, str.Неважно, кто это делает, вы или компилятор.

Просто сделайте это сами, я не могу понять, как компилятор сделает это за вас.Компиляторам, в том числе gcc, довольно трудно выполнять определенный доступ, который, как вам кажется, говорят:

*(volatile unsigned int *)(SOME_ALIGNED_ADDRESS)=some_value;

Мой синтаксис, вероятно, неверен, потому что я отказался от него несколько лет назад, но он не всегда выдает неподписанныйint size store, и когда компилятор не хочет, он не будет.если он не может сделать это надежно, как вы можете ожидать, что он создаст один набор загрузок и хранилищ для этой переменной или структуры и другой вариант для этой переменной или структуры?

Итак, если у вас есть конкретные инструкции, вам нужен компилятор дляпроизводить, у вас не получится, вы должны использовать ассемблер, точка.В частности, ldm, ldrd, ldr, ldrh, ldrb, strd, str, strh, strb и stm.

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

Я также рекомендую попробовать другие компиляторы, если вас беспокоит такая большая оптимизация.Как минимум попробуйте gcc 3.x, gcc 4.x, llvm и rvct, которые, я думаю, есть версия, которая поставляется с Keil (но не знаю, как она сравнивается с реальным компилятором rvct).

Я не чувствую, насколько маленьким должен быть ваш двоичный файл.Если вам нужно упаковать вещи в nvram и вы не можете создать все 32-битные записи, я бы порекомендовал несколько вспомогательных функций ассемблера, один вариант get32 и put32, два варианта get16 и put16 и четыре варианта get8 и put8.Вы будете знать, как пишете код, в который упакованы вещи, так что вы можете писать код напрямую или с помощью макросов / определяет, какой вариант get16 или put8.Эти функции должны иметь только один параметр, так что при их использовании не требуется затрат места на коде, производительность в виде сброса канала в ветви, в зависимости от вашего вида ядра.Что я не знаю, так это то, что эти 50 или 100 инструкций функций put и get нарушают ваш бюджет размера кода?Если так, то мне интересно, стоит ли вам вообще использовать C?В частности, gcc.

И вы, вероятно, захотите использовать thumb вместо arm, если размер очень важен, thumb2, если он у вас есть.

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

Какое ядро ​​вы используете?Я недавно работал с кем-то из семейства arm 11 с шиной Axi, и рука действительно хорошо справляется с преобразованием последовательностей ldrs, ldrbs, ldrhs и т. Д. В отдельные 32- или 64-битные чтения (да, несколько отдельных инструкций могут превратиться водин цикл памяти).Возможно, вам просто придется адаптировать свой код к функциям ядра, в зависимости от ядра и того, где находится этот интерфейс для поддержки памяти nvram.Для этого пришлось бы сделать много симов, я знаю это только по тому, что смотрел на автобус, а не из документации по руке.

1 голос
/ 02 ноября 2010

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

Что-то вроде следующего (непроверенного - даже не скомпилированного) кода:

uint8_t nvram_get_u8( uint8_t const* p)
{
    uint32_t const* p32 = ((uintptr_t) p) & (~0x03);    // get a 32-bit aligned pointer
    int bit_offset = (((uintptr_t) p) & 0x03) * 8;      // get the offset of the byte 
                                                        //      we're interested in

    uint8_t val = ((*p32) >> bit_offset) & 0xff;

    return val;
}


void nvram_set_u8( uint8_t* p, uint8_t val)
{
    uint32_t* p32 = ((uintptr_t) p) & (~0x03);  // get a 32-bit aligned pointer
    int offset = (((uintptr_t) p) & 0x03) * 8;  // get the offset of the byte 
                                                //      we're interested in

    uint32_t tmp = *p32;

    tmp &= ~(((uint32_t) 0xff) << bit_offset);  // clear the byte we're writing
    tmp |= val << bit_offset;                   // and 'or' in the new data

    *p32 = tmp;

    return;
}

Теперь вы можете читать / писать что-то вроде myBar.evenSmaller (при условии, что компоновщик / загрузчик выложил myBar так, что он находится в адресном пространстве NVRAM), например:

uint8_t evenSmaller = nvram_get_u8( &myBar.evenSmaller);

nvram_set_u8( &myBar.evenSmaller, 0x5a);

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

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

1 голос
/ 02 ноября 2010

Самое простое, что можно сделать, это использовать объединение.

typedef union something {
    struct useful {
        uint8_t one;
        uint8_t two;
    };
    struct useless {
        uint32_t size_force[1];
    };
} something;
void something_memcpy(something* main_memory, something* memory_in_nvram) {
    for(int i = 0; i < sizeof(main_memory->useless.size_force); i++) {
        memory_in_nvram->useless.size_force[i] = main_memory->useless.size_force[i];
    }
}

Один из них - только пример - вы могли бы написать некоторую арифметику, которая будет сделана во время компиляции, чтобы автоматически определить размер.Читайте и пишите с NVRam с точки зрения бесполезного члена, но всегда обращайтесь к нему в основной памяти с точки зрения «настоящего» полезного члена.Это должно заставить компилятор читать и записывать 32 бита за раз (каждый 32 бита в массиве в бесполезной структуре), но все же позволит вам легко и безопасно вводить данные из реальных элементов данных.

0 голосов
/ 02 ноября 2010

Вы, вероятно, можете сделать это, если сделаете все битовым полем:

uint32_t something;
uint32_t somethingSmaller:16;
uint32_t evenSmaller:8;
uint32_t pad:8;  // not strictly necessary but will help with your sanity

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

...