关于我
欢迎来到我的博客!这里汇集了我对编程和技术的洞见和总结。本站内容分为几个主要类别,涵盖从具体技术实现到编程理念的广泛话题。
主要内容分类
- 项目工程:深入探讨技术的实现细节和理解。
- C/C++:围绕C/C++语言的技术点和编程技巧进行详细总结。
- 程序员哲学:分享程序员在职业生涯中应该具备的哲学理念和思考方式。
想要了解更多具体内容,您可以访问文章分类页面。
联系我
如果您有任何问题或想要交流,欢迎通过关于页面与我联系。
感谢您的阅读和支持,希望我的博客能为您的技术旅程带来帮助!
volatile在访存的作用
volatile作用的变量,会在访问的时候,直接读取/写入对于内存,在对于现代计算机来说是一件缓慢的事.一般都是存储在cache中,并且采取一定的策略进行写回内存.
如果不使用volatile,如果变量指向的是一个IO设备的寄存器,如果这个变量被映射到cache并且cache策略是写回策略.
那么我们就无法及时的控制IO设备.
关于fce…中的mario和kernels中的hello运行现象
如果将klib中的宏定义后,可以运行mario,如果没有定义,则不能运行.
说明,如果将宏定义后,将会自动将klib中的关键运行库链接进去,大部分还是运行在真机上(ARC=native)
运行hello时,通过统一的API,访问规则下的内存进行读写.有被模拟的设备(代码),对这样的读写行为进行了抽象,这就是模拟的设备,这个设备的实现依然依赖真机的GNU/Linux运行时环境.
再次深度理解代码
在经过了ftrace的实现之后,每一个第一个"记录"函数,就是_trm_init函数,现在,在遇到mainargs的传参问题时,来总体的分析一下这个过程.
整个ysyx项目中,不仅有c文件,makefile文件,mk文件,readme文件,各种elf等中间文件,其实还有余博写的汇编代码文件.整个nemu的启动,就是开始于这个start.S文件.
这里有一个jal指令,跳转到了_trm_init函数.
这里的_tram_init函数调用了main函数,并且传入了mainargs参数.
这个mainargs就是一个const char **类型,
main函数,只接受了args参数,并且分析了这个参数.
在amtests中,main函数主要是通过switch函数,来匹配对应的值,然后调用对应的函数.
switch中是一个CASE宏,专门用来匹配一个首字母和整个词的对应的.
mainargs值的来源
首先mainargs是一个环境变量,通过检索,在init_platform函数中,调用了getenv函数来获取mainargs环境变量的值.
在使用中,我们在命令行中给mainargs的值都是文件的名字,或者说可以定位这个程序的地址.
首先在make工程中,mainargs的值是经过处理(目录地址)然后被赋值给ASFLAGS变量的,ASFLAGS是在编译.S文件中,汇编编译使用的.这里带上了这个目标文件.
同样的CFLAGS也是被赋予了mainargs变量的值,也就是说,我们在命令行中定义的mainargs,其实是被不同的环境变量给按照不同的方式拿走了.
现在被CFLAGS拿走,那就更好说了,因为CFLAGS本来就是传给main中的args变量的.
在am-tests中,我们在mainargs中给了一个程序,然后这个程序其实在汇编编译阶段被编译进去,然后在main中的switch代码块中,被匹配到对应的文件名中的函数.然后调用这个程序文件.
第一段中提到的,在init_platform函数中,调用了getenv函数来获取了mainargs环境变量的值,但是这个init_platform函数并没有被调用.这应该是一个后续需要完成的一个任务.
mainargs在main中被传递
不同的文件,比如kernels中的hello的main,在获取了mainargs的参数后,就是当成一个字符串用来输出.在am-test中的hello.c文件,就是用来匹配了文件名.上述提到了mainargs中主要用来传递函数名是不对的.
关于printf宏的实现想法
已经实现了关于sprintf函数
sprintf声明:int sprintf(char *out, const char *fmt, …);
printf函数的声明:int printf(const char *fmt, …);
printf函数和sprintf函数的实现部分,有很大程度上是相似的.
sprintf:
识别fmt中的每一个字符,然后按照要求/规则一个一个写入out中.
printf函数:
识别fmt中的每一个字符,然后按照要求/规则直接/处理后打印出去.
printf函数也可以有另一种实现,调用sprintf函数,然后for循环打印out字符串即可.
实现过程
printf函数需要stdarg头文件,并且使用了变参系统,具体传参格式就是(fmt, …)
而sprintf同样如此,传参格式也是(fmt, …)
在printf中获得的参数会被处理成一个va_list args变量.
想要过去变量的值,就通过va_arg函数来获得.
如果想要将获得的变参传递给sprintf,那么只能传入va_list类型的args变量.
这样每一次调用sprintf,传入的是sprintf(out, fmt, args);
这里的args并不匹配fmt,因为args是一个变量(虽然里面是一个数组/链表类型的).这就让args只能匹配一个格式字符.
所以这会导致匹配问题,编译的时候出现内存溢出.
那么我就将其实现成宏的形式:
1 |
|
这个宏经过测试,其功能是正确的,只不过可能当字符串太长(超过255)时,会发生溢出.
处理这个printf链接问题
很多地方调用printf,是看成函数来调用的,并且要包含stdio头文件,在stdio头文件中是包含了printf函数的声明.
将printf函数实现成宏,那我们必须要处理避免让程序去包含stdio头文件中关于printf函数的定义.所以我们干脆将所有的包含stdio头文件的程序(如am-test中的alutest)换成包含klib头文件.
klib头文件中声明了自己编写的io函数.我们应该把printf宏放到这个文件中.
因为在应用的时候,是默认当成函数的,所以要包含klib.h.而不是要包含klib-macros.h头文件.
所以放入了klib.h中的printf宏,有一个问题,就是宏内部是调用了putstr宏.
为了避免不必要的麻烦和过多的干扰(我可能不太熟悉这里面的关系,尽可能简化,以达到单纯的测试这一个功能).我将putstr宏展开:
1 |
|
在简单测试集中(自己编写的一个printf测试函数),是完全通过测试的,但是在使用alu-test进行测试的时候出现如下错误:
1 | + LD -> build/alutest-riscv32-nemu.elf |
这是一个编译阶段比较深的时候出现的问题,具体因为什么,我不熟悉编译原理.
咨询gpt,得到的回答:这个错误是RISC-V架构特有的链接问题,通常出现在32位RISC-V系统中。错误信息表明链接器无法正确处理JAL(Jump and Link)指令的重定位…
链接脚本可能没有正确地安排代码段…
这两段是我觉得可能比较正确的回答.
上面是ARCH=riscv32-nemu的结果.如果编译到native时会一直出现:
1 | In file included from /home/bruce/project-a/git-project/ysyx-workbench/abstract-machine/am/src/native/ioe/audio.c:4: |
关于这个问题,我修改了printf宏的不同代码块包含.用do{}while(0),或者加入更多的括号,但是都是错误的.
这个问题先遗留下来
所以,现在打算实现函数的printf.
printf函数实现
由于传入的fmt是const类型,所以,我们不应该在fmt上修改格式字符串.
与sprintf函数相比,printf又不需要得到一个转换良好的out字符串.
为了更好的性能,直接将处理好的字符输出出去,而不是拷贝到另一个buf中,然后等待统一输出.
这里的实现很简单.
主要是复用了putstr宏,itoa_special函数.
其中putstr宏有一个漏洞,就是内部宏的实现是声明了一个局部变量p,作为迭代器.
所以在传入putstr宏参数的时候,不可以是p名称.否则会报错.
1 | char *p = va_arg(args, char*); putstr(p); |
便会报错
时钟实现
关于在__am_timer_uptime中实现关于读取系统运行时间.
三个基本操作:
获取到系统启动的时间
获取到现在时间
计算时间间隔.
在该函数的上面是__am_timer_init函数,我怀疑就是用来初始化,并且获取系统启动时间的.
在rtc.c文件中调用了io_read宏,然后该宏调用了ioe_read函数.
观察到,ioe_init函数中是调用了__am_timer_init函数.
所以这进一步证实了其作用.
并且关于运行rtc文件,其实__am_timer_uptime实现正确与否,与此无关系.因为我试着随便给读取出来的数加值,rtc依然运行正确.
所以__am_timer_init函数必须实现.
实现之后,经过输出调试,发现系统启动时间就是0;
补充printf实现
将原先的sprintf的实现,转移到vsprintf实现上.
因为vsprintf的接口是接受一个va_list变量,这样就可以让printf和vsprintf调用.
之前遇到的问题就是:printf和sprintf的接口一致,都使用了变参系统,是无法相互调用的.
其次,printf中的%f是无法实现的.
在benchmacro测试集中,是出现了输出浮点数的例子.
但是还没有来得及实现%f,文档中也没有提这件事.
看了测试源码,发现源码中有fmt函数将浮点自动转换了整数分段输出.
这种简单的事,为何不让我们自己实现.
当我实现的时候,发现报错了:
1 |
|
这是在浮点转换为整数的时候,一个编译错误.
我打算先将这个问题搁置.
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !