我正在为嵌入式系统编写软件。
我们使用指针来访问 FPGA 设备的寄存器。
一些寄存器是只读的,而另一些是只写的。
只读寄存器在读取时将产生未定义的值。
我想定义一个指针类型,它允许编译器检测何时从只写寄存器读取值(也称为取消引用)。
只能使用 C 语言语法创建只写指针吗?
(我们正在使用 C 开发第一个原型,但在第二代转向 C++。)
如何在 C++ 中创建高效的只写指针?(请记住,这不是跟踪动态内存中的项目,而是访问硬件地址。)
此代码用于安全和质量最受关注的嵌入式系统。
我正在为嵌入式系统编写软件。
我们使用指针来访问 FPGA 设备的寄存器。
一些寄存器是只读的,而另一些是只写的。
只读寄存器在读取时将产生未定义的值。
我想定义一个指针类型,它允许编译器检测何时从只写寄存器读取值(也称为取消引用)。
只能使用 C 语言语法创建只写指针吗?
(我们正在使用 C 开发第一个原型,但在第二代转向 C++。)
如何在 C++ 中创建高效的只写指针?(请记住,这不是跟踪动态内存中的项目,而是访问硬件地址。)
此代码用于安全和质量最受关注的嵌入式系统。
我可能会为每个编写一个小包装类:
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
我将使用结构组合来表示寄存器和一对函数来处理它们。
在一个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);
在 xor 中使用 READ 和 WRITE 来表示权限。
比在fpga_register.c
你会定义一个新的结构
typedef struct register_t2 {
char permissions;
void * address;
} FPGARegisterReal;
以便您返回指向它的指针而不是指向FPGARegister
on的指针fpga_init
。
然后,fpga_read
你fpga_write
检查权限和
FPGARegister
则将参数从参数转换回 a FPGARegisterReal
,执行所需的操作(设置或读取值)并返回成功代码这样,包括头文件在内的任何人都无法访问该FPGARegisterReal
结构,因此它无法直接访问寄存器地址。显然,可以破解它,但我很确定这种故意破解不是您真正关心的问题。
我使用过很多硬件,其中一些具有“只读”或“只写”寄存器(或不同的功能,具体取决于您是读取还是写入寄存器,当有人决定这样做时会很有趣) reg |= 4;" 而不是记住它应该具有的值,而是设置位 2 并写入新值,就像你应该的那样。没有什么比尝试调试具有随机位出现和消失的硬件的硬件了,你无法读取!; ) 到目前为止,我还没有看到任何实际阻止从只写寄存器读取或写入只读寄存器的尝试。
顺便说一句,我是否说过拥有“只写”的寄存器是一个非常糟糕的主意,因为您无法回读以检查软件是否正确设置了寄存器,这使得调试非常困难 - 并且人们编写驱动程序不喜欢调试可以通过两行 VHDL 或 Verilog 代码变得非常容易的难题。
如果您对寄存器布局有一定的控制权,我建议您将“只读”寄存器放在 4KB 对齐的地址中,将“只写”寄存器放在另一个 4KB 对齐的地址中[超过 4KB 也可以]。然后您可以对硬件的内存控制器进行编程以防止访问。
或者,如果正在读取不应该读取的寄存器,或者写入不应该写入的寄存器,则让硬件产生中断。我认为硬件确实会为其他目的产生中断?
使用各种 C++ 解决方案提出的其他建议很好,但它并没有真正阻止打算直接使用寄存器的人,所以如果这真的是一个安全问题(而不是“让我们让它变得尴尬”),那么你应该有硬件,以防止硬件被滥用。
在 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]
可以提供写入值的函数。
我在 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
}
Dan Saks 有一个 Youtube 演示文稿,我无法找到他在哪里介绍嵌入式设备的只写寄存器。
幸运的是他写了一篇更容易搜索的文章,在这里:https ://www.embedded.com/how-to-enforce-write-only-access/
这是文章中的代码,针对 C++11 更新
class write_only_T{
public:
write_only_T(){}
write_only_T(T const& v) : m(v){}
write_only_T(T&& v) : m(std::move(v)){}
write_only_T& operator=(T const& v){
m = v;
return *this;
}
write_only_T& operator=(T&& v){
m = std::move(v);
return *this;
}
write_only_T(write_only_T const&) = delete;
write_only_T(write_only_T&&) = delete;
write_only_T& operator=(write_only_T const&) = delete;
write_only_T& operator=(write_only_T&&) = delete;
private:
T m;
};
如果您使用它,我认为您不需要特殊的指针类型,因为只写是值的属性,但我可以想象一个跳过值类型的合成指针类型。您可能需要引入只写引用类型等。