2013-04-17 2 views
52

Я пишу программное обеспечение для встроенной системы.Тип указателя только для записи

Мы используем указатели для доступа к регистрам устройства FPGA.
Некоторые регистры доступны только для чтения, а другие - только для записи.

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

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

Может ли указатель на запись быть создан только с использованием синтаксиса языка C?
(Мы разрабатываем первый прототип с использованием C, но переходим на C++ на 2-ом поколении.)

Как создать эффективный указатель на запись только в C++? (Помните, что это не отслеживание элементов в динамической памяти, а доступ к аппаратным адресам.)

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

+0

У вас также есть регистры чтения и записи, верно? –

+8

Я серьезно сомневаюсь, что вы можете это сделать на C. В C++, с другой стороны, вы должны сделать это относительно легко. – dasblinkenlight

+1

@dasblinkenlight В C и на C++ единственным решением является дисциплина. Запись '* p = ...' или '... = * p' всегда короче и соблазнительна, чем писать и использовать некоторые специальные макросы, функции или классы (возможно, классы шаблонов). –

ответ

61

Я бы, наверное, написать крошечный класс-обертку для каждого:

template <class T> 
class read_only { 
    T volatile *addr; 
public: 
    read_only(int address) : addr((T *)address) {} 
    operator T() volatile const { return *addr; } 
}; 

template <class T> 
class write_only { 
    T volatile *addr; 
public: 
    write_only(int address) : addr ((T *)address) {} 

    // chaining not allowed since it's write only. 
    void operator=(T const &t) volatile { *addr = t; } 
}; 

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

read_only<unsigned char> x(0x1234); 
write_only<unsigned char> y(0x1235); 

y = x + 1;   // No problem 

x = y;    // won't compile 
+0

Красивые шаблоны! Как вы комбинируете их для регистров чтения и записи? –

+0

Почему вы решили использовать 'int', а не' volatile T * '? Если вы действительно хотите целое число, существует 'intptr_t'. Кроме того, 'operator T()' может быть 'const volatile'. –

+3

@JonPurdy: Я избегал 'T volatile *', потому что это означало бы, что пользователь имеет указатель чтения/записи в регистр - именно этого мы и хотели избежать. Встроенные компиляторы часто несколько ... ограничены, поэтому ожидая, что они будут включать 'intptr_t' (только что добавленный в C++ 11), задают много вопросов. Во всяком случае, я бы сделал его параметром шаблона. Я соглашаюсь на 'const volatile' - только что отредактирован; Спасибо. –

8

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

В fpga_register.h вы бы что-то вроде

#define FPGA_READ = 1; 
#define FPGA_WRITE = 2; 
typedef struct register_t { 
    char permissions; 
} FPGARegister; 

FPGARegister* fpga_init(void* address, char permissions); 

int fpga_write(FPGARegister* register, void* value); 

int fpga_read(FPGARegister* register, void* value); 

с читать и писать в исключающем выразить разрешения.

чем в fpga_register.c вы бы определить новую-структуру

typedef struct register_t2 { 
    char permissions; 
    void * address; 
} FPGARegisterReal; 

так что вы возвращает указатель на него вместо указателя на FPGARegister на fpga_init.

Затем на fpga_read и fpga_write проверять разрешения и

  • если operetion допускается, отлитый назад FPGARegister от аргумента к FPGARegisterReal, выполнить нужное действие (установить или прочитать значение) и возвращает код успеха
  • если операция не разрешена, просто возвращает код ошибки

Таким образом, никто, включая файл заголовка не сможет получить доступ к FPGARegisterReal, и поэтому он не будет иметь прямого доступа к адресу регистра. Очевидно, можно было взломать его, но я совершенно уверен, что такие намеренные взломы - это не ваши актуальные проблемы.

8

Я работал с большим количеством аппаратного обеспечения, и некоторые из них имеют регистры «только для чтения» или «только для записи» (или разные функции в зависимости от того, читаете или записываете в регистр, что делает забаву, когда кто-то решает сделать «reg | = 4;» вместо того, чтобы запомнить значение, которое оно должно иметь, установить бит 2 и записать новое значение, как и вы. Ничто не похоже на попытку отладки аппаратного обеспечения, которое имеет случайные биты, появляющиеся и исчезающие из регистров, t read!;) Я до сих пор не видел попыток блокировки чтения из регистра только для записи или записи в регистры только для чтения.

Кстати, я сказал, что наличие регистров, которые являются «только для записи», является ДЕЙСТВИТЕЛЬНО плохой идеей, потому что вы не можете прочитать, чтобы проверить, правильно ли установлено программное обеспечение, что делает отладочную работу очень трудной - и люди, пишущие драйверы, не любят отлаживать сложные проблемы, которые могут быть сделаны очень легко двумя строками кода VHDL или Verilog.

Если у вас есть некоторый контроль над макетом регистра, я бы предположил, что вы помещаете регистры «только для чтения» с адресом с выравниванием по 4 КБ, а регистры «writeonly» регистрируются в другом адресе с 4 КБ [более 4 КБ в порядке]. Затем вы можете запрограммировать контроллер памяти на оборудовании, чтобы предотвратить доступ.

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

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

+1

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

+0

@Owen: По-прежнему полезно иметь возможность прочитать «что было последним, что я написал в этом регистре». Но да, я согласен, есть некоторые реестры, где это не имеет особого смысла. –

+0

Наличие регистров только для записи - это прекрасная идея, если регистр записывает триггер * действия *. Хотя может быть полезно иметь регистры только для чтения, которые затем сообщают о статусе этих действий, и такие регистры могут даже передавать адрес в регистры только для записи, адрес не имел бы регистр чтения-записи, а скорее регистр только для чтения и отдельный регистр записи. – supercat

6

Я не вижу элегантный способ сделать это в C. Однако я вижу способ сделать это:

#define DEREF_PTR(type, ptr) type ptr; \ 
typedef char ptr ## _DEREF_PTR; 

#define NO_DEREF_PTR(type, ptr) type ptr; \ 

#define DEREFERENCE(ptr) \ 
*ptr; \ 
{ptr ## _DEREF_PTR \ 
attempt_to_dereference_pointer_ ## ptr;} 

int main(int argc, char *argv[]) { 
    DEREF_PTR(int*, x) 
    NO_DEREF_PTR(int*, y); 

    DEREFERENCE(x); 
    DEREFERENCE(y); // will throw an error 
} 

Это имеет преимущество, давая вам статическую проверку ошибок. Конечно, используя этот метод, вам придется выйти и изменить все ваши объявления указателей на использование макросов, что, вероятно, не очень весело.

Редактировать: Как описано в комментариях.

#define READABLE_PTR(type, ptr) type ptr; \ 
typedef char ptr ## _READABLE_PTR; 

#define NON_READABLE_PTR(type, ptr) type ptr; \ 

#define GET(ptr) \ 
*ptr; \ 
{ptr ## _READABLE_PTR \ 
attempt_to_dereference_non_readable_pointer_ ## ptr;} 

#define SET(ptr, value) \ 
*ptr = value; 


int main(int argc, char *argv[]) { 
    READABLE_PTR(int*, x) 
    NON_READABLE_PTR(int*, y); 

    SET(x, 1); 
    SET(y, 1); 

    int foo = GET(x); 
    int bar = GET(y); // error 
} 
+0

Это не отличает чтения и записи. –

+0

Упс, это правильно. Я видел его упоминание об обнаружении разыгрышей и вроде того, что меня опередил. –

+0

Однако этот же принцип можно использовать для определения макросов для чтения и установки значения позади указателя. –

7

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


/* writeonly.h */ 
typedef struct writeonly *wo_ptr_t; 

/* writeonly.c */ 
#include "writeonly.h" 

struct writeonly { 
    int value 
}; 

/*...*/ 

    FOO_REGISTER->value = 42; 

/* someother.c */ 
#include "writeonly.h" 

/*...*/ 

    int x = FOO_REGISTER->value; /* error: deref'ing pointer to incomplete type */ 

Только writeonly.c, или вообще любой код, который имеет определение struct writeonly, может разыменовать указатель. Этот код, конечно, также может случайно прочитать значение, но, по крайней мере, любой другой код не позволяет разыменовывать указатели вместе, имея возможность передавать эти указатели и хранить их в переменных, массивах и структурах.

writeonly.[ch] может предоставить функцию для записи значения.