本帖最后由 wuliangu 于 2025-4-22 17:56 编辑
#申请原创#
@21小跑堂
iCatch V39M的方案知道吗?它的程序是怎么跑的?一起来了解下吧 前言:本文仅对初学者而言适用,希望能让其对V39M程序的了解有所帮助。 我们以前学习单片机程序编程的时候就知道了一件事:那就是程序都是从main函数开始执行的。所以我们习惯上在看程序时首先就是去找main函数入口。但随着后续的深入学习和了解,终于知道这程序实际上并不是从main函数开始执行的!程序在运行main函数之前是要先进行程序加载,然后再进行一系列的初始化这些。当然,V39M的程序也不例外,也不是直接从main函数开始执行的。现在的编程模式,一般都是模块化的编程方式。这里先找到V39M程序包中的主函数文件(其文件名为app_main.c) 。我们在app_main.c文件中找到main函数,然后在函数中前面的位置上加一句打印信息,编译好再烧录到机器里面,接好串口线上电抓取机器运行的LOG信息,如下图: 图(一) 从图中可以看出,当跑到main函数这个地方,串口打印信息都跑到了第162行左右的位置了,这可以说明在运行main函数之前还是做了很多其它的事。 那么,V39M的程序是怎么运行的呢?它的执行顺序是怎么样的?跟着来发挥一点探究精神,一起去了解下吧! 我以前的文章中也提到过,V39M的程序架构是消息驱动类的,其基本框架如下图: 图(二) 图中左边是对消息的处理,右边是产生消息及应用层对相关功能的实现。 系统上电后会先执行相关预备工作,然后产生一个消息,这个消息会存入到消息列表里等待main函数来处理,main函数收到消息后会通过Switch切换到相应功能的实现。 打开SDK,先来搜一下有“main”字样的文件有哪些?结果如下图搜到有8个文件: 图(三) 从文件名和路径来看符合预期的文件那就是“app_main.c”这个文件了。那好,我们就以这个文件为中轴线,找到里面的main函数,往前或往后来追溯这程序的执行过程。在分析的过程中,我们一定要利用好串口信息这个工具,因为串口信息每一行的出现都是按时间顺序对应程序的相应执行位置的,所以为了方便分析,我们也可以在程序相应位置加上打印信息,以方便观察相应功能在什么时候执行。那么我们就来开始追踪吧。 在文中前面第一张图和描述中我们知道了main函数的位置,那在main函数前面是运行的什么呢?先往前追溯,我们看到appMain()的括号里面是有参数的,那这些参数从哪个位置传递过来的呢?这里追踪到了“sp5k_job.c”这个文件,在里面的第121行开始调用appMain函数,如下图, 图(四) 在这个文件的sp5kJobDo()函数中第102行加上打印信息,从前面图(一)中的串口信息来看,所对应的打印信息是在第148行,图(四)中的第103到111行对应了串口信息的第149到161行,这些都是运行在appmain函数之前的。那么,sp5kJobDo()函数之前又是运行什么呢? 从串口信息第147行来看程序是跑到了”appOmfActivateCommand”这里,那我们就来找下这句是在SDK的哪个文件中。如下图(五)中我们找到了3处地方, 图(五) 从所属文件来看,在项目中我们用到了“omfRegCmd.c”“app_init_xip.c”这两个文件。打开文件看下“appOmfActivateCommand”所在的位置,是在“omfRegCmd.c”文件中的第7行打印的 图(六) 从图中来看,“appOmfActivateCommand”是在appOmfActivateCommand()函数中的最前面,该函数的功能是初始化、加载或者执行某些操作来支持 OMF (开放媒体框架)数据的交互功能,说白了就是和音视频的初始化有关,涉及到的一些命令设置。 而appOmfActivateCommand()函数又是在“app_init_xip.c”这个文件的第393行(如图(七)) 图(七) app_init_xip.c文件中的xip(eXecute In Place)是指可执行代码在存储器运行的一项技术,说白了这个文件就是和存储方面的初始化有关。 图(八) 串口信息图(八)中的第146行,是在哪里运行的呢? 这是从app_init_xip.c这个文件里的第392行(如图(七))调用appOmfActivatePlugin()这个函数进入到了“omfRegister.c”这个文件中,如下图(九)在文件中的第45行打印了串口信息第146行。这涉及到了多媒体接入状态的设置。总的来说appOmfActivatePlugin()和appOmfActivateCommand()这两个函数是对多媒体一些寄存器的设置。 图(九) 在分析过程中发现appOmfActivatePlugin()和appOmfActivateCommand()这两个函数不止在app_init_xip.c这个文件中有调用, 在“app_init.c”这个文件中也有调用,如下图(十)的第414行和第415行,第410行的注释意思是“媒体注册”。 图(十) 既然在app_init_xip.c和app_init.c文件中都有调用,那实际上究竟是从哪个文件来执行的呢?我们不凡在两个文件的appOmfActivatePlugin()函数前加上打印信息,如下图(十一) 图(十一) 从串口信息执行结果来看程序是执行app_init.c这个文件的。 图(八)中第143行和第144行的信息又是来自哪里呢?虽然没有找到打印的具体位置,但从内容来看第143行是对OMF的命令处理程序的注册,第144行是对C++运行时间的初始化。既然没找到位置,那么继续往前看图(八)第142行所在的位置,如下图(十二)通过搜索“appImgWindowInit_Chn_0”这个信息 图(十二) 搜索结果可以看到这信息是在“app_osd_init.c”这个文件中,而具体打印信息是在appImgWindowInit_Chn_0_Set()这个函数中,而这个函数在appOsdInit_DUALModeSet( )和appOsdInit_LCDModeSet()这两个函数中有调用,那到底这里是在哪个中调用的呢?我们可以在这两个函数中加上打印信息来判断,结果是在appOsdInit_LCDModeSet()这个函数中调用的。从appOsdInit_LCDModeSet()这个函数一开始就有如下 printf("appOsdInit_LCDModeSet wSize = %d hSize = %d\n",wSize,hSize); 这个打印信息来看,其对应的是图(八)中的第131行,所以说第131行到第142行都是在这个函数里面执行的结果。那么appOsdInit_LCDModeSet()这个函数又是在哪里被调用的呢? 在文件中鼠标右键点击这个函数再左键点Jump To Caller这项,如下图(十三) 图(十三) 搜到的结果如下图(十四)可以看到有7处地方使用了这个函数,那就在每个地方的位置 图(十四) 之前加上打印信息,看究竟是从哪里进的这个函数。结果可以看出是从“app_power_on.c”这个文件中进入的,如下图(十五) 图(十五) 图(十五)中运行完appOsdInit_LCDModeSet()这个函数后就会运行appOsdInit()这个函数, 一进入appOsdInit()这个函数第一行就会打印[host]osdinit: init :s这个信息,而图(八)中第143行和第144行的信息介于[debug]app_osd_init.c/appImagWindowInit和[host]osdinit: init :s这个信息之间,所以说图(八)中第143行和第144行是在“app_osd_init.c”这个文件中执行的。从图(十五)中可以看出appOsdInit_LCDModeSet()函数是在devDispEnum_task()这个函数中调用的,那么devDispEnum_task()这个函数又是在哪里被调用的呢?往前查到是在 devDispEnum_thrInit()这个函数中调用的。而devDispEnum_thrInit()这个函数又是在“dev_init.c”这个文件中调用的。在图(十五)中的第1224行devDispEnum()函数前加上打印信息再查看串口信息中是从哪里开始的,从串口信息可以看出从 “customization/arch/v37/dev_init.c Line 351. devDispEnum(). FIXME V37. ”这句信息到 “[host]osdinit: init :s”这句之间都是在app_power_on.c文件的devDispEnum_task()函数中执行的。前面查到devDispEnum_thrInit()函数是在dev_init.c这个文件中被调用,那就来看下具体是在哪个位置吧?查看dev_init.c文件可知devDispEnum_thrInit()函数是在devInitPrimary()函数中,那就在这函数中前面位置加上打印信息来看下会出现在串口信息中的哪个位置,如下图(十六) 图(十六) 结果如下图(十七),好家伙!原来这个函数在串口信息文件中的第5行,在程序执行顺序中这么靠前! 图(十七) 从字面意思来看,devInitPrimary()函数中的“primary”单词意思是“主要的、最早的、原始的”,所以这个函数的意思就是“最早的设备初始化”。那么我们再来看看这函数中都是执行了些什么功能呢? devInitPrimary()函数中被调用函数按出现的顺序有: codeSentry() 主要用于监控程序流的安全性和完整性。它的入口条件 是MULTI_BOOT_STAGE为0,且RAM_PROGRAM为1时。 sioCtrl() 串行输入输出 (SIO, Serial Input Output) IO的控制。 其入口条件是HOST_XIP_SUPPORT为1时。 coreSightInit() coreSight系统初始化。Coresight是ARM公司提出的一种用于 复杂SoC(System on Chip)的调试和跟踪架构。
cpuDebugWPInit() CPU调试功能初始化。 SysInit_InitEn_Crypt() 加密功能初始化。入口条件是DM2016为1时。 sp5kProfLogInit(0, 32768) 初始化指定主机配置文件日志插槽。 DEV_PROF_LOG_PRINT(0, "#S#%s#N#", __FUNCTION__) 记录当前时间戳并将printf结果写入指定的配置文件日志槽。 sp5kPwrDetectGet(0xFFFFFFFF, &powerOnStatus) 获取电源检测引脚的状态。固件系统初始化期间的电源状态。 DEV_PROF_LOG_PRINT() 日志打印。 接下来跟着一些日志打印函数: dbgPrintLogInit() dbgPrintLogConfirmCallbackSet() dbgPrintLogFnameSet() dbgPrintLogFnameSet() 接着是加载函数: multibootCb() 是一个回调函数,用于在 Multiboot 过程中的某个特定阶段执行特定操作。 multibootLoadWait() 涉及设置加载地址、发送加载命令以及等待加载完成等步骤。 入口条件是MULTI_BOOT_STAGE为1时。 devRtcInit() RTC功能初始化。 sp5kAdcInit() ADC功能初始化。 devGpioInit(bEnterUsbPowerOn) USB上电时对GPIO进行初始化。 devBatteryCheck() 对电池进行检查。 devDispEnum_thrInit() 用于初始化与显示枚举相关的线程。 devDispEnum() 显示枚举。含显示模块初始化这些。 devFrontEnum() 前摄像头枚举。 hostInitStartCbSet() 设置在主机启动上执行的回调函数。 hostInitSensorWaitSet() 控制是否通过传感器初始化阻止主机启动。 hostMsgMaxNumSet() 设置主机消息队列大小。 hostInit() 主机初始化。 我们在最后一个调用函数hostInit()的前后加上打印信息,如图(十八) 图(十八) 可以看到两个信息之间没有其它内容,而在源码上也没有找到hostInit()的具体内容,所以这个函数要么就是一个空函数,要么就是被封装到了底层没有加打印信息,不过这并不影响我们对程序的理解及使用。 我这里能追溯到的最早运行函数就是devInitPrimary()这个了,再往前是从哪里运行的就不得而知了,可能还是对这SDK不熟所致,但从前面几行的信息来看,这应该是系统选择硬件存储准备挂载系统的过程,这里我找了使用两种存储器件的串口信息做对比,如下图(十九) 图(十九) 从信息上可以看出一个是使用的SPI器件,一个使用的是EMMC器件,SPI的器件地址是ef4018,而EMMC器件的地址则没有打印出来。
总结:从上面的分析可以看出程序的执行顺序是先是识别硬件,然后挂载系统,然后进入到dev_init.c文件中运行devInitPrimary()这个函数,在devInitPrimary()中运行其它函数时会跳转到相对应的文件中。最后到app_main.c文件,在这文件中做各种消息的处理。 从串口信息可以看出程序运行调用的文件顺序如下: dev_init.c -> app_power_on.c -> app_osd_init.c -> app_init.c -> omfRegister.c -> sp5k_job.c -> app_main.c (注:串口信息不会细致到每一个函数的运行情况,但大致方向是按顺序的。如附件是上电开机较完整版的串口信息文件,其中带“[debug]”前缀的行是后面为了定位加上的。)
最后,由于篇幅较长,有些内容并未交代清楚,还有就是作者能力的局限性,所以,有不足的地方还请见谅! 这里也非常感谢您的观阅!如有问题,欢迎指正!
|