1、任务
1.1、任务创建
- 任务自启动参数:
内核提供的任务创建接口,会存在参数指定当前任务创建完立即运行还是需要显示调用start运行,需要注意。如果在创建任务时指定了立即执行,而在创建任务后去设置任务参数,可能是不生效的。(尤其posix的pthread接口经常遇到这种问题)
- 任务的栈参数:
任务的栈大小是由用户来指定的,一定要确认开栈大小是否满足自身需求。OS虽然有栈溢出检测,但是不是100%能检测出来的。引起的问题会非常混乱。
1.2、任务的调度策略
目前rhino内核提供的几种调度策略包括:
- 优先级;
- RR时间片;
- CFS公平调度
需要注意的是,在前两种调度的任务中,如果用户任务内在死循环,且没有主动退出的动作,如sleep或者其他Pend动作,那这个任务会一直运行,直到中断的到来,才可能切换到更高优先级的任务,且低优先级任务永远无法运行。
1.3、任务和中断关联
任务执行中,如果不关中断,中断会随时打断任务上下文现场;
中断退出时,会发生任务调度,可能会有任务切换。
1.4、任务退出
需要关注的是,用户的任务处理函数内结束后,任务即会自动退出;
不需要用户手动调用删除任务接口;
也不能在任务自己退出后,还在使用任务的资源;
2、内存
2.1、内存空间管理
内存的堆一般是在ld链接脚本中预留的,通过固定的数据结构告诉OS。
链接脚本中定义堆空间:
-
PROVIDE (heap_start = __stack); //end of stack
-
heap_end = ORIGIN(RAM) + LENGTH(RAM);
-
PROVIDE (heap_len = heap_end - heap_start);
堆的起点heap_start定义为栈的结尾,堆的结尾heap_end定义为RAM的结尾,这样剩余RAM的空间都交给OS管理。
对应的krhino的堆空间初始化为:
注意:这段内存分配给堆使用,并不是表示内存都耗尽了,而是将其交给OS管理,用户通过malloc出来的内存都是从其中申请。
2.2、内存分配管理
内核提供的内存申请返回地址和大小是8字节对齐的,上限受到总的堆空间的限制。
每一块的内存blk有管理头,用来提供基本的管理和维测信息。
内部会自动完成相关块的拆分合并,但是不可避免会有内存碎片的问题,需要通过下面的快速小内存来优化。
2.3、内存维测
通过内存头部的维测模块的信息,来对该内存块进行监控,主要包含的信息包括:
内存魔术字 => 判断是否被踩
申请的任务号 => 跟踪任务申请状况
申请的时间段 => 跟踪某时间段的内存申请释放情况
申请的backtrace => 具体是哪个位置申请的内存
当前内存状态: => 申请还是释放
具体参考:k_mm.h中k_mm_list_t数据结构
2.4、小内存快速申请
内核还额外提供快速内存申请功能。其提供可配置的2^N固定大小的内存快速申请和释放,优点是效率高,没有碎片,缺点是没有维测信息。
2.5、内存踩踏
内核中提供了自动对内存块的检测,看其内存头是否正确,来达到检测内存踩踏的目的,一般检测在下面的时间点发生:
内存释放:会检测当前内存块的魔术字是否被踩;检测下一块内存头是否正确;检测内存空闲/忙状态是否正确。
在发生内存致命错误或者异常时:
系统会遍历所有内存块,将可疑内存给检测输出出来。
会存在检测遗漏的现场:
尤其是用户空间被飞踩,则检测内存头部检测不出来;
也会经常存在内存头部的非魔术字信息被踩的情况。
从串口打印看是否有内存踩踏:
目前从串口的输出信息查找下面两个关键字,可快速查看是否系统检测出内存踩踏:
-
!used !free
-
======== all memory error blocks =========
-
kernel space mem layout:
-
g_kmm_head = 40ccce60
-
ALL BLOCKS
-
Blk_Addr Stat Len Chk Caller Point
-
0x4f9176e0 used 2368 OK 0x40526c58 (0x40526c54 <- 0x40626730 <- 0x40627160 <- 0x4061aba0 <- 0x4061b228 <- 0x4061b8a8 <- 0x4061be40 <- 0x40615450)
-
0x4f918050 free 48 OK 0x0 (0x40654b90 <- 0x40664964 <- 0x40664b64 <- 0x40561aa8 <- 0x0 <- 0x0 <- 0x0 <- 0x0)
-
0x4f9180b0 !used 56 a848 0x406528d4 (0x406528d0 <- 0x40664964 <- 0x40664b64 <- 0x40664b60 <- 0x0 <- 0x0 <- 0x0 <- 0x0)
如上图,检测出0x4f9180b0内存状态不对,此时一般首先怀疑被前面的内存块给踩了。当然这只是最基本的情况。
可以通过上面Caller打印出来的调用栈,通过addr2line命令输出。也可通过OS提供的自动解析脚本和IDE工具输出。
2.6、内存泄漏
当内存申请失败的时候,系统报致命错误,并将当前剩余空间,已经当前的内存状态都打印出来。
内存不足报错:
所有的内存状态信息打印:
-
------------------------------- all memory blocks ---------------------------------
-
g_kmm_head = 805985a0
-
ALL BLOCKS
-
Blk_Addr Stat Len Chk Caller Point
-
0x80598688 used 8 OK 0x0 (0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0)
-
0x805986c0 used 1049952 OK 0x0 (0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0)
-
0x80698c50 used 3112 OK 0x8000a448 (0x8000a444 <- 0x800084fc <- 0x80000780 <- 0x0 <- 0x0 <- 0x0 <- 0x0 <- 0x0)
通过我们提供的python解析脚本,能自动检测可以内存泄漏点,使用方法:
把上面log 和对应 elf取出,和AliOS Things工程内提供的解析脚本 coredump_parser_mmleak.py放在同一目录
执行脚本:
分析结果样例:
-
========== Show MM LEAK Info : Top 10 of Malloc-Cnt Backtrace ==========
-
--------------------------------------------------------------------------------------------------------------------------------------------
-
{'Backtrace': '0x80290880 <- 0x8029f8b4 <- 0x802a0f2c <- 0x802a131c <- 0x80296ad8 <- 0x0 <- 0x0 <- 0x0', 'Blk Cnt': 18320, 'Total Size': 1124424}
-
js_def_malloc at thirdparty/quickjs/quickjs.c:34194 (discriminator 1)
-
========== Show MM Statistic Info ==========
-
Caller | Func | Alloc Cnt | Total Size | Line | File
-
0x802fd8c4 | sk_malloc_flags(unsigned int, unsigned int) | 249 | 22265888 | 263 | thirdparty/skia/src/core/SkMemory_stdlib.cpp
可以将目前申请次数最大,以及申请内存总数最大的位置导出,供用户进一步分析。
2.7、堆和栈
内核中的堆指的是划分给内存malloc申请的内存空间;
栈一般是任务运行的自身的栈空间,用作用户函数执行过程中的压栈出栈处理;
另外还有CPU相关的模式栈、系统栈之类的,用户暂不用关注。
3、中断
3.1、中断必调接口
一般在CPU架构代码中已经默认调用,但是一定要确保中断上下文调用过。
-
krhino_intrpt_enter(); //指示系统进入中断上下文
-
isr_handler();
-
krhino_intrpt_exit(); //内部会进行切换动作
3.2、中断中失效接口
Sem、mutex、queue的等待接口不能在中断中使用,因为其会调度当前的task,而目前正处于中断上下文;当然OS可以封装一层不触发调度的基本等待接口,但是一旦没有等待信息,会立即返回失败,没有等待的效果。
对应的释放接口可以在中断中使用。
3.3、中断优先级
目前中断设定为同样的优先级,不支持中断抢占。
3.4、中断开关
用户接口不应该直接调用开关中断的接口,而全部内核接管。
3.5、Fiq和irq
普通的中断一般叫irq,位于GIC架构的Group1;另外ARM还提供的快速中断fiq,位于Group0,其默认优先级高于所有的irq中断,能打断任意中断和任务现场
4、多核相关
4.1、任务相关接口
内核提供所有的对外接口都可以在单核或者多核场景下使用,且能到达相关的目的。
如mutex接口能同时保障单核和多核的互斥。
同时还提供了4个基本的多核扩展接口:
krhino_task_cpu_create |
任务静态创建,并指定核运行 |
krhino_task_dyn_create |
任务动态创建,并指定核运行 |
krhino_task_cpu_bind |
任务绑定核运行 |
krhino_task_cpu_unbind |
任务解绑核运行,运行核随机 |
4.2、多核互斥接口
原则上,用户使用通用的mutex接口来进行互斥,不直接使用和系统运行深度关联的关中断、加锁功能接口,以免造成不必要的死锁。
对于死锁问题,尤其要排查用户显示调用加锁的地方是否有问题。
4.3、加锁和关中断
加锁接口前必定要关中断:
对于普通核内不可嵌套锁,中断触发后再次加锁会导致死锁;
对于核内嵌套锁,如果不关中断,内部发生调度,该任务被调度到其他核,那么就会导致解锁失败。
同样的原理:关中断加锁接口内,只能有普通的数据、寄存器操作,不能调用会发生切换的接口,如mutex、printf、malloc。
5、用户态/内核态
内核态与用户态权限隔离、页表隔离
任务的三种运行状态:用户态任务、内核态任务、用户态任务的内核模式;
用户态异常现场在SVC,说明进入了内核态之后异常,得查看内核态的现场;
拿到异常现场需要首先确认异常发生在用户态还是内核态。
判断方法:
- 查看异常PC和相关通用寄存器空间处于用户态还是内核态;
- 从异常打印现场区分
内核会在异常时检测是用户态还是内核态异常,区分关键字 :
“kernel space exception” : 内核态发生异常
“uspace \*\* exception” : 用户态发生异常
“uspace task result exception in kernel” : 用户态任务系统调用进入内核发生异常
开发者技术支持
如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号
更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/