Newlib
目录
Newlib是一个面向嵌入式系统的C运行库。最初是由Cygnus Solutions收集组装的一个源代码集合,取名为newlib,现在由Red Hat维护,目前的最新的版本是2.1.0。
对于与GNU兼容的嵌入式C运行库,Newlib并不是唯一的选择,但是从成熟度来讲,newlib是最优秀的。newlib具有独特的体系结构,使得它能够非常好地满足深度嵌入式系统的要求。newlib可移植性强,具有可重入特性、功能完备等特点,已广泛应用于各种嵌入式系统中。
Newlib的所有库函数都建立在20个桩函数的基础上[2],这20个桩函数完成一些newlib无法实现的功能:
1) 级I/O和文件系统访问(open、close、read、write、lseek、stat、fstat、fcntl、link、unlink、rename);
2) 扩大内存堆的需求(sbrk);
3) 获得当前系统的日期和时间(gettimeofday、times);
4) 各种类型的任务管理函数(execve、fork、getpid、kill、wait、_exit);
这20个桩函数在语义、语法上与POSIX标准下对应的20个同名系统调用是完全兼容的[3]。成功移植newlib的关键是在目标系统环境下,找到能够与这些桩函数衔接的功能函数并实现这些桩函数。
Newlib为每个桩函数提供了可重入的和不可重入的两种版本。两种版本的区别在于,如果不可重入版桩函数的名字是xxx,则对应的可重入版桩函数的名字是_xxx_r,如close和_close_r,open和_open_r,等等。此外,可重入的桩函数在参数表中含有一个_reent结构指针,这个指针使得系统的实现者能在库和目标操作环境之间传送上下文相关的信息,尤其是发生错误时,能够便捷的传送errno的值到适当的任务中。
所谓最小实现是指,假定将要移植的目标系统中没有文件系统,也没有符合POSIX标准的任务管理机制和应用编程接口(Application Programming Interface, API),仅仅实现newlib的一个最小移植。在newlib的移植过程中全功能实现的桩函数只有open、close、read、write和sbrk五个,其他桩函数仅仅实现一个返回错误的空函数。
任务管理的execve、fork、getpid、kill、wait和_exit六个桩函数,仅仅实现一个返回-1的空函数,返回之前将errno设置为ENOTSUP,表示系统不支持该函数。
与文件相关的link和unlink桩函数也仅仅实现一个返回-1的空函数,将errno设置为EMLINK表示连接过多;lseek函数则不需要返回任何错误,直接返回0,表示操作成功。
fstat和stat桩函数在newlib中主要用于判断流的类型(常规文件、字符设备、目录),将其实现为不论输入参数如何,都返回字符设备类型的空函数。
times桩函数返回当前进程中的各种时间信息,如果目标系统中的任务不能提供类似的时间信息,仅仅实现一个返回-1的空函数,将errno设置为ENOTSUP。
由于newlib认为在目标系统中fcntl、rename和gettimeofday三个桩函数缺省是不提供的,所以也不提供这三个桩函数的实现。
C运行库的可重入性问题主要是库中的全局变量在多任务环境下的可重入性问题,Newlib解决这个问题的方法是,定义一个struct _reent类型的结构,将运行库所有会引起可重入性问题的全局变量都放到该结构中。而这些全局变量则被重新定义为若干个宏,以errno为例,名为“errno”的宏引用指向struct _reent结构类型的一个全局指针,这个指针叫做_impure_ptr。
对于用户,这一切都被errno宏隐藏了,需要检查错误时,用户只需要像其他ANSI C环境下所做的一样,检查errno“变量”就可以了。实际上,用户对errno宏的访问是返回_impure_ptr->errno的值,而不是一个全局变量的值。
Newlib定义了_reent结构类型的一个静态实例,并在系统初始化时用全局指针_impure_ptr指向它。如果系统中只有一个任务,那么系统将正常运行,不需要做额外的工作;如果希望newlib运行在多任务环境下,必须完成下面的两个步骤:
1) 每个任务提供一个_reent结构的实例并初始化;
2) 任务上下文切换的时刻重新设置_impure_ptr指针,使它指向即将投入运行任务的_reent结构实例。
这样就可以保障大多数库函数(尤其是stdio库函数)的可重入性。如果需要可重入的malloc,还必须设法实现__malloc_lock()和__malloc_unlock()函数,它们在内存分配过程中保障堆(heap)在多任务环境下的安全。
大多数嵌入式操作系统都实现了自己的动态内存分配机制,并且提供了多任务环境下对内存分配机制的保护措施,如果移植newlib到这样的系统时,可以放弃newlib自带的malloc函数。尽管newlib自带的malloc非常高效,但是几乎所有的用户都习惯使用malloc来作为动态内存分配器。在这种情况下,最好对系统自带的动态内存分配API进行封装,使它不论在风格、外观上,还是在语义上都与malloc完全相同,这对于提高应用程序的可移植性大有好处。
对于那些没有实现动态内存分配机制的嵌入式系统环境来说,newlib的malloc是一个非常好的选择,只需实现sbrk桩函数,malloc就可以非常好地工作起来。与之同名的POSIX系统调用的作用是从系统中获得一块内存,每当malloc需要更多的内存时,都会调用sbrk函数。
在单任务环境下,只需实现sbrk桩函数,malloc就可以正常运行;但在多任务环境下,还需实现__malloc_lock()和__malloc_unlock()函数,newlib用这两个函数来保护内存堆免受冲击。用户可利用目标环境中的互斥信号量机制来实现这两个函数,在__malloc_lock()函数中申请互斥信号量,而在__malloc_unlock()函数中释放同一个互斥信号量。
附件列表
故事内容仅供参考,如果您需要解决具体问题
(尤其在法律、医学等领域),建议您咨询相关领域专业人士。
