结合AliOS Things谈嵌入式系统通用问题定位方法(2):内核相关基础

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的堆空间初始化为:

k_mm_region_t g_mm_region[] = {{(uint8_t *) &heap_start, (size_t) &heap_len}};

注意:这段内存分配给堆使用,并不是表示内存都耗尽了,而是将其交给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、内存泄漏

当内存申请失败的时候,系统报致命错误,并将当前剩余空间,已经当前的内存状态都打印出来。

内存不足报错:

WARNING, malloc failed!!!! need size:80, but free size:40

所有的内存状态信息打印:
 

------------------------------- 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放在同一目录

执行脚本:

./coredump_parser_mmleak.py err.log alios_kernel\@mt8153a-mk.kernel.elf

分析结果样例:


========== 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/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值