更新时间:2021-11-28 08:44:16
内核提供的任务创建接口,会存在参数指定当前任务创建完立即运行还是需要显示调用start运行,需要注意。如果在创建任务时指定了立即执行,而在创建任务后去设置任务参数,可能是不生效的。(尤其posix的pthread接口经常遇到这种问题)
任务的栈大小是由用户来指定的,一定要确认开栈大小是否满足自身需求。OS虽然有栈溢出检测,但是不是100%能检测出来的。引起的问题会非常混乱。
目前rhino内核提供的几种调度策略包括:
需要注意的是,在前两种调度的任务中,如果用户任务内在死循环,且没有主动退出的动作,如sleep或者其他Pend动作,那这个任务会一直运行,直到中断的到来,才可能切换到更高优先级的任务,且低优先级任务永远无法运行。
任务执行中,如果不关中断,中断会随时打断任务上下文现场;
中断退出时,会发生任务调度,可能会有任务切换。
需要关注的是,用户的任务处理函数内结束后,任务即会自动退出;
不需要用户手动调用删除任务接口;
也不能在任务自己退出后,还在使用任务的资源;
内存的堆一般是在ld链接脚本中预留的,通过固定的数据结构告诉OS。
链接脚本中定义堆空间:
堆的起点heap_start定义为栈的结尾,堆的结尾heap_end定义为RAM的结尾,这样剩余RAM的空间都交给OS管理。
对应的krhino的堆空间初始化为:
注意:这段内存分配给堆使用,并不是表示内存都耗尽了,而是将其交给OS管理,用户通过malloc出来的内存都是从其中申请。
内核提供的内存申请返回地址和大小是8字节对齐的,上限受到总的堆空间的限制。
每一块的内存blk有管理头,用来提供基本的管理和维测信息。
内部会自动完成相关块的拆分合并,但是不可避免会有内存碎片的问题,需要通过下面的快速小内存来优化。
通过内存头部的维测模块的信息,来对该内存块进行监控,主要包含的信息包括:
内存魔术字 => 判断是否被踩
申请的任务号 => 跟踪任务申请状况
申请的时间段 => 跟踪某时间段的内存申请释放情况
申请的backtrace => 具体是哪个位置申请的内存
当前内存状态: => 申请还是释放
具体参考:k_mm.h中k_mm_list_t数据结构
内核还额外提供快速内存申请功能。其提供可配置的2^N固定大小的内存快速申请和释放,优点是效率高,没有碎片,缺点是没有维测信息。
内核中提供了自动对内存块的检测,看其内存头是否正确,来达到检测内存踩踏的目的,一般检测在下面的时间点发生:
内存释放:会检测当前内存块的魔术字是否被踩;检测下一块内存头是否正确;检测内存空闲/忙状态是否正确。
在发生内存致命错误或者异常时:
系统会遍历所有内存块,将可疑内存给检测输出出来。
会存在检测遗漏的现场:
尤其是用户空间被飞踩,则检测内存头部检测不出来;
也会经常存在内存头部的非魔术字信息被踩的情况。
从串口打印看是否有内存踩踏:
目前从串口的输出信息查找下面两个关键字,可快速查看是否系统检测出内存踩踏:
如上图,检测出0x4f9180b0内存状态不对,此时一般首先怀疑被前面的内存块给踩了。当然这只是最基本的情况。
可以通过上面Caller打印出来的调用栈,通过addr2line命令输出。也可通过OS提供的自动解析脚本和IDE工具输出。
当内存申请失败的时候,系统报致命错误,并将当前剩余空间,已经当前的内存状态都打印出来。
内存不足报错:
所有的内存状态信息打印:
通过我们提供的python解析脚本,能自动检测可以内存泄漏点,使用方法:
把上面log 和对应 elf取出,和AliOS Things工程内提供的解析脚本 coredump_parser_mmleak.py放在同一目录
执行脚本:
分析结果样例:
可以将目前申请次数最大,以及申请内存总数最大的位置导出,供用户进一步分析。
内核中的堆指的是划分给内存malloc申请的内存空间;
栈一般是任务运行的自身的栈空间,用作用户函数执行过程中的压栈出栈处理;
另外还有CPU相关的模式栈、系统栈之类的,用户暂不用关注。
一般在CPU架构代码中已经默认调用,但是一定要确保中断上下文调用过。
Sem、mutex、queue的等待接口不能在中断中使用,因为其会调度当前的task,而目前正处于中断上下文;当然OS可以封装一层不触发调度的基本等待接口,但是一旦没有等待信息,会立即返回失败,没有等待的效果。
对应的释放接口可以在中断中使用。
目前中断设定为同样的优先级,不支持中断抢占。
用户接口不应该直接调用开关中断的接口,而全部内核接管。
普通的中断一般叫irq,位于GIC架构的Group1;另外ARM还提供的快速中断fiq,位于Group0,其默认优先级高于所有的irq中断,能打断任意中断和任务现场
内核提供所有的对外接口都可以在单核或者多核场景下使用,且能到达相关的目的。
如mutex接口能同时保障单核和多核的互斥。
同时还提供了4个基本的多核扩展接口:
krhino_task_cpu_create |
任务静态创建,并指定核运行 |
krhino_task_dyn_create |
任务动态创建,并指定核运行 |
krhino_task_cpu_bind |
任务绑定核运行 |
krhino_task_cpu_unbind |
任务解绑核运行,运行核随机 |
原则上,用户使用通用的mutex接口来进行互斥,不直接使用和系统运行深度关联的关中断、加锁功能接口,以免造成不必要的死锁。
对于死锁问题,尤其要排查用户显示调用加锁的地方是否有问题。
加锁接口前必定要关中断:
对于普通核内不可嵌套锁,中断触发后再次加锁会导致死锁;
对于核内嵌套锁,如果不关中断,内部发生调度,该任务被调度到其他核,那么就会导致解锁失败。
同样的原理:关中断加锁接口内,只能有普通的数据、寄存器操作,不能调用会发生切换的接口,如mutex、printf、malloc。
内核态与用户态权限隔离、页表隔离
任务的三种运行状态:用户态任务、内核态任务、用户态任务的内核模式;
用户态异常现场在SVC,说明进入了内核态之后异常,得查看内核态的现场;
拿到异常现场需要首先确认异常发生在用户态还是内核态。
判断方法:
内核会在异常时检测是用户态还是内核态异常,区分关键字 :
“kernel space exception” : 内核态发生异常
“uspace \*\* exception” : 用户态发生异常
“uspace task result exception in kernel” : 用户态任务系统调用进入内核发生异常
如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号
更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/