理想的跨平台库
人们必须创建一个新的软件库。该库没有系统 I/O 例程,仅包含 Init(config)、Run(some_data_inputs) 和 DeInit(config) 函数以及一些 typedef 和定义。 Run() 函数只是处理给定的输入数据并生成输出数据,没有中断、线程、锁等。但是,它需要一些 RAM 来满足其内部需求。该接口在头文件中描述,静态库包含实现。
该库必须尽可能多平台。将该库移植到任何新平台、任何新操作系统+CPU 上必须只需要几分钟的时间。有时这个库甚至可以在没有操作系统的平台上使用。
因此,在开始开发之前,必须决定:
- 这个库必须用 ANSI-C 编写,甚至必须用 ANSI-C 的子集编写,因为某些平台可能不完全支持 ANSI-C?
- 是否写抽象 层,它封装了一个 底层操作系统+CPU?
- 这个抽象层应该 包括自己的内存管理器 (malloc/free 例程)或全部 今天的平台已经提供了吗?
One has to create a new software library. This library has no system I/O routines and contains only Init(config), Run(some_data_inputs), and DeInit(config) functions and a few typedefs and defines. Run() function just processes given input data and generates output data, no interrupts, threads, locks, etc. However, it requires some RAM for its internal needs. The interface is described in a header file and a static library contains the implementation.
This library must be as much multi-plarform as possible. It must be matter of minutes to port this library to any new platform onto any new OS+CPU. Sometimes this lib can be used even on the platforms without operating systems.
So, before starting development, one has to decide:
- Must this lib be written in ANSI-C or even in a sub-set of ANSI-C because some platforms may not support ANSI-C completely?
- Whether to write an abstraction
layer, which encapsulates an
underlying OS+CPU? - Should this abstraction layer
include its own memory manager
(malloc/free routines) or all
platforms today provide it already?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我也不建议使用 ISO C。自 1990 年以来,ANSI 就不再是 C 的标准机构。ISO C90 应该无处不在。根据目标平台的功能,随附的标准库可能是一个子集,或者可能需要实现存根才能将其移植到特定目标。 Newlib C 库通常在使用 GCC 的嵌入式系统中使用,需要基本的 I/O 存根(在本例中不需要,因为您已指定不进行 I/O 操作)和要实现的 sbrk() 函数。 sbrk() 为堆分配器提供内存;它不需要操作系统,或者可以从操作系统请求内存。
鉴于您对此库设计施加的限制,似乎没有操作系统问题。标准库提供了内存分配的要求。
在(1)中回答;将它们作为标准库的一部分提供或使用平台的现有实现。
最后,您需要编写库以使用 C90 和标准库,然后只需将标准库移植到目标(如果尚未完成)即可。
Neither, I suggest ISO C. ANSI has not been the standards body for C since 1990. ISO C90 should be ubiquitous. Depending on the target platform capabilities, the accompanying Standard Library may be a subset, or may require implementation of stubs to port it to a particular target. The Newlib C library, often used in embedded systems using GCC for example, requires basic I/O stubs (not required in this case since you have specified that no I/O operations take place) and the sbrk() function to be implemented. sbrk() provides memory to the heap allocator; it requires no OS, or it could request memory from an OS.
Give the restrictions you have imposed on this library design, it seems that the are no OS issues. The standard library provides the requirements for memory allocation.
Answered in (1); provide them as part of the standard library or use the existing implementation for the platform.
In the end what you need is to write your library to use C90 and the standard library, then it is simply a matter of porting the standard library to the target if it is not already done.
按相反顺序:
(3)。您需要自己的内存管理例程,如果不是出于任何其他原因,那么仅仅是因为您希望它可以移植到没有底层操作系统的裸平台上。然后,您还需要几乎所有其他内容的实现:字符串库、数学库等。您必须自己编写它们的代码,或者尝试找到为每个平台提供它们的现成代码库。
(2)。操作系统和 CPU 在某种程度上是正交变量。您可能会更好地创建两个抽象层(一个用于不同的操作系统,一个用于不同的硬件平台),然后根据每个新平台的需要包含/覆盖定义。但是,是的,抽象层是比用大量 #ifdef 填充代码更易于管理的解决方案。
(1).这不是一个容易回答的问题。例如,如果您希望您的库在嵌入式系统甚至微控制器上运行,那么很可能并非所有 ANSI C 功能都可用,具体取决于开发工具的成熟度。还可能存在不一定与语言本身相关的限制。硬件浮点单元在嵌入式系统中相对较少。堆栈大小可能受到限制等。我建议您对您感兴趣的平台进行调查,并尝试选择一个公共子集。
(0)。从经济(甚至可行性)的角度来看,您很可能会发现,最好是针对最常见的目标平台支持的相当丰富的子集进行开发,并在遇到新平台时重构您的代码。尝试将您自己限制在最常见子集上可能会严重削弱您的开发工作以及在功能稍强的系统中库的有效性。
(-1)。您应该从具有所需可移植性级别的库的稀缺性(甚至完全缺乏)中认识到,您想要实现的目标并不容易。做好准备!
In reverse order:
(3). You need your own memory management routines, if not for any other reason, then simply because you expect that it may be ported to a bare platform without an underlying OS. Then you also need an implementation for pretty much everything else: string libraries, math libraries etc. You have to either code them yourself, or try to find ready-made code libraries that provide them for each platform.
(2). The OS and CPU are somewhat orthogonal variables. You would probably have a better time creating two abstraction layers (one for different operating systems, one for different hardware platforms) and then include/override definitions as necessary for each new platform. But yes, an abstraction layer is a more manageable solution than riddling your code with whole hordes of #ifdefs.
(1). This is not an easy question to answer. For example if you expect your library to run on an embedded system or even a microcontroller then it's quite probable that not all of ANSI C features are available, depending on the maturity of the development tools. There could also be restrictions not necessarily related to the language itself. Hardware floating point units are relatively rare in embedded systems. Stack sizes could be limited etc. I suggest that you make a survey of the platforms that you are interested in and try to select a common subset.
(0). You could very well find from an economic (or even a feasibility) point of view that it is preferrable to develop for a rather rich subset that is supported in your most common target platforms and refactor your code if you encounter a new platform. Trying to restrict youserlf to the most common subset could essentially cripple both your development effort and the effectiveness of your library in slightly more capable systems.
(-1). You should realise from the scarsity (or even complete lack) of libraries with your required level of portability that what you want to achieve is not going to be easy. Be prepared!
有许多不同风格的系统架构,编写一些可以轻松移植到所有这些架构的不平凡的东西可能是不可能的。
不过,我建议您编写接口,以便它们接收指向包含所有所需系统函数的结构的指针。您可以在每次调用中执行此操作,也可以在 init 例程中仅执行一次。
这样,您就可以将不同体系结构的代码差异的地方限制为生成函数指针结构的代码。
这也使得您可以通过仅更改此结构来轻松测试库以处理一个或多个例程的失败,因此,如果您想测试对 malloc 失败的正确处理,那么您只需替换结构与失败的一个。
您还可以将系统函数包装在特定系统上,其中的函数为您提供库所需的接口以及至少部分处理错误(例如与
errno
之间的转换)。There are lots of different styles of system architecture, and writing something non-trivial that is easily portable to all of them is probably not possible.
I suggest, though, that you write your interfaces so that they take in a pointer to a structure that contains all of the system functions that are needed. You could do this either in every call or just once in the init routine.
This way you will have limited the places where differences in the code for different architectures to code which generates the structure of function pointers.
This also makes it so that you can easily test the library for handling failure of one or more of these routines by altering just this structure, so that if you wanted to test for proper handling of malloc failure then you just replace the malloc pointer in the struct with one that fails.
You might also wrap the system functions on a particular system with functions that give you the interface your library expects as well as at least partially handling errors (like translating to/from
errno
).