先实践后理论,我使用一块CPE301G-QCA9535的开发板进行分析。这块开发板是在开发中的ecos。
一)宏观分析ecos的运行
我们从启动日志入手,去一步一步了解ecos的运行。
总所周知,板子(soc)启动有3阶段。分别为运行ROM上的固化程序,运行u-boot-spl 和运行uboot。所以我们打包的uboot的名称经常叫uboot-with-spl.bin。细节可以自行百度。
运行ROM上的固化程序阶段没有打印,这就是为什么板子能从其他地方加载系统的原因。
板子启动spl+uboot阶段,打印如下:
程序就Now running in RAM - U-Boot at: 807dc000,开始运行uboot了。
初始化串口和网卡
开始加载镜像
开始运行kernel
程序开始运行一个应用程序了,这个程序ecosap这个可执行程序。
这样我们从宏观上看到了整个ecos的运行情况。
二)从代码层面分析ecos的运行
在qca9535_ecos_sdk/ecos/ecosap 目录下有个ecosap.c文件,文件里面有个main函数。因为ecos只能运行一个程序,所以就只能运行ecosap这个可执行文件了。如下图:
怎么运行这个ecosap文件呢?我们看一下它的文件格式是我们熟悉的elf,我们知道运行elf是需要ELF Loader的,ecos应该是没有这个东西的;毕竟ecos只需运行一个程序就万事大吉了。格式如下图:
然后我就去看make 编译的打印,被我发现了一些线索。如下图:
Objcopy就是把裸的机器码拷贝出来,啥说明和格式都不要。要想运行裸的机器码,第一步加载到内存;第二步从该地址进行运行。这下我知道ecosap是脱了衣服成ecosap.bin之后再运行的。
给一个运行裸文件的例子:
RedBoot> lo -b 0x81010000 -r -m xmodem
Raw file loaded 0x81010000-0x810252cb, assumed entry at 0x81010000
xyzModem - CRC mode, 679(SOH)/0(STX)/0(CAN) packets, 2 retries
RedBoot> go 0x81010000
不可能每次上电后,手敲运行。那就打一个包,自动加载运行。
我们发现这些编译控制都在eCosap.mk中,如下图。先对ecosap进行使用lzma进行压缩,然后使用mkimage进行打包出一个ecosap.lzma.uImage_201910231807包,这个只是一个内核,不带uboot;也没有根文件系统。
mkimage的基本参数如下:
这样子就实现了使用uboot自动将kerenl加载到指定RAM地址中,然后运行就好了。这样就万事大吉了。
三)验证一下上面的分析
3.1再回首uboot加载image的过程
一顿操作猛如虎,一看战绩0-5。我们回头看一下uboot加载image的截图,如下。
你会发现参数都是我们使用mkimage生成的。Image Name 就是我们使用mkimage写的-n eCosAP;Image Type:MIIPS Linux Kernel Image (lzma compressed);Load Address 0x80000000,Entry Point 0x800001bc世界真小。
见证奇迹的时刻,解压 内核的大小 1183484bytes,我们看一我们裸奔的ecosap.bin的大小就是1183484bytes。这就对上了。
然后就开始在0x800001bc上疯狂的裸奔。
3.2再回首kernel的裸奔
上面是在运行ecosap的main-->ecosap_start--> print Starting AP之前的打印,这些函数在哪里运行的呢?
现在探究一下上面的问题。看了一下代码,原来是利用c++的类的构造函数+使用__attribute__((init_priority(_pri_)))属性来实现在main函数之前运行一些初始化。这下终于揭开了一个裸奔的ecos的正面目。