对于嵌入式newlib中printf的一个小思考

最近在做一个关于RISCV嵌入式项目的开发,RISCV平台用的是一个picorv32的一个开源小核,代码只有不到三千行,但是功能基本完备,非常适合在FPGA中做一些和控制相关的操作。

为了程序开发方便,我在编译CPU程序的时候,直接使用了newlib来实现一些标准的C库程序,这样虽然程序效率不高,好在常用的C库程序不需要单独开发,我们只需要将我们的精力集中在应用程序开发上就好,并且在程序测试的时候,我们也可以直接在PC上调用本地的标准C库,实现程序测试,对于这部分内容也挺多,基本也可以单开一个很大的章节,这里就不详述了。

其实我遇到的问题是在引入标准C库的时候碰到了,我们知道,一般要实现libc,我们需要重新定义一些系统调用相关的函数,比如针对printf我么就需要实现_write函数,这个我还是按照之前的写法进行操作,然而我发现我定义了_write函数之后,我调用printf,输出过程并没有调用_write函数,当然信息也就不能输出到串口上了。

通过艰难的debug,我发现printf最终调用了一个类似_fake_stdout的变量上面,这个搞得我有点摸不着头绪,后来找了很多资料才发现这个问题是因为printf的缓存机制导致的。

其实标准库中我们调用printf的时候,printf子函数并不会直接将数据打印到串口,而是会经过一个缓存,重写底层系统调用的时候我们知道,我们要实现一些文件操作,并且对于_write操作其实也是往文件里面写的,只不过写的目标文件是一个stdout或者stderr的特殊文件。所以C库对这些文件进行操作的时候,中间会经过一个缓存,这样就可以避免对文件的多次读写,提高系统的效率,其实是一种非常好的设计。

不过这种设计,对于我们这种小白用户就可能造成一些困扰,因为这个buffer一般是1KB大小,所以我们常常打印了很多字符也看不到,因为我们实际都是写在buffer里面了,除非显示的调用flush或者字符结尾加上\n,系统才会将这些字符打印到我们的目标文件,也就是串口上。

我在一直过程中就直接写了一个printf("Hello world!"),就是因为没有加上\n,所以这些字符全都写到了buffer上。串口完全没反应,中间几度放弃,还好最终找到了这个原因。想想,这个问题其实以前应该也会碰到,不过之前的程序打印的时候内容都比较多,中间都有\n所以没发现这个问题。

另外针对于这个问题,解决方法一种就是在字符串结尾加上\n,另一种就是直接把这个buffer关掉,总之要记住这里有个坑就好了。


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

相关文章

发表新评论