当前位置:首页 > 芯闻号 > 充电吧
[导读]前言: 最近一直都在看nucleus plus,之前看过一些linux内核的一些东西,但都是停留在文字上,代码看的很少,这个nucleus plus内核的代码量不大,看过source code确实对

前言: 最近一直都在看nucleus plus,之前看过一些linux内核的一些东西,但都是停留在文字上,代码看的很少,这个nucleus plus内核的代码量不大,看过source code确实对很多OS的知识有了更深入的认识,收获还是挺多的,把学到的东西记录下来。

内容:

 一、nucleus plus特点: 1.内核采用微内核的设计,方便移植,资料写着更reliable,但是我不这么认为,与linux相比,以ARM平台为例,NU只用到了SVC mode,内核与用户任务都运行在同一个状态下,也就是说所有的task都拥有访问任何资源的权限,这样很reliable么? 2.real-time OS,NU是一个软实时操作系统(VxWorks是硬实时),thread control component支持多任务以及任务的抢占,对于中断的处理定义了两种服务方式,LISR和HISR,这个与linux中的上、下半部机制类似,linux中的下半部是通过软中断来实现的,NU的HISR只是作为一种优先级总是高于task的任务出现。 3.NU是以library的方式应用的,通过写自己的app task与裁剪后的NU内核及组件链接起来,NU并没有CLI

二、组件

1.IN component 初始化组件由三个部分组成,硬件在reset后首先进入INT_initialize(),进行板级的相关初始化,首先设置SVC mode,关中断,然后将内核从rom中拷贝至ram中,建立bss段,依次建立sys stack, irq stack和fiq stack,最后初始化timer,建立timer HISR的栈空间,看了一下2410平台的代码,一个tick大概是15.8ms,完成板级的初始化后就进入了INC_initialize,初始化各个组件,其中包括Application initialize,create task和HISR,最后将控制权交给schedule,主要看了一下RAM中地址空间的安排 |timer HISR stack = 1024| |FIQ stack = 512| |IRQ stack = 1024| |SVC stack = 1024| |.bss| |.data| |.text| 其中SVC stack的大小与中断源的个数相关,nested irq发生时,irq_context保存在SVC stack中,IRQ的stack只是做了临时栈的作用。

2.thread control component TC组件是NU内核的最重要组成部分,主要涵盖了调度、中断、任务的相关操作、锁、时钟几个方面,下面分别介绍。

调度(schedule)

NU中的线程类型(在同一个地址空间内)有两种,HISR和task,HISR可以理解为一种优先级较高的task,但又不是task,HISR优先级高于task的实现方式就是schdule时,先去查看当前是否有active的HISR,再去查看task。task有suspend、ready、finished和terminated四种状态,而HISR只有executing和no-active这两种状态。

每一个task都有一个线程控制的数据结构(TCB thread control block),其中包括了task的优先级、状态、时间片、task栈、protect信息、signal操作的标志位和signal_handler等,task在创建时初始化这些信息,将task挂到一个create_list上,初始设定task为pure_suspend,如果设定auto start,调用resume_task()唤醒task,这里有个细节,如果在application initialize中create_task(),则task不会自动运行,因为初始化还未完成,控制权还没有交给schedule,无法调度task。task被唤醒后状态改变为ready,并挂在一个TCD_Priority_List[256]上,数组的每个元素是一个指向TCB环形双向链表的指针,根据task的tc_priority找到对应优先级的TCB head pointer。

每一个HISR都有一个HISR控制的数据结构(HCB HISR control block),其中只有优先级,HISR栈和HISR entry信息,因此HISR是不可以suspend,同时也没有time slice以及signal的相关操作,一般情况下当发生了中断后,HISR被activate,schedule就会调度HISR运行,期间如果不发生中断,HISR的执行是不会被打断的,HISR的优先级只有0、1、2,timer的HISR优先级为2,也就是说由外部中断激活的HISR很难被抢占的,只有更高优先级的中断HISR才可以。与task不同,被激活的HISR使用head_list和tail_list将HCB挂在一个单项的链表上,因为相同优先级的HISR不会抢占对方,因此不需要双向链表,使用两个指针目的是加快HISR执行的速度。


一个实时操作系统的核心就是对于任务的调度,NU的调度策略是time slice和round robin的算法, 调度的部分主要有三个函数control_to_system()用于保存上下文,建立solicited stack,关中断,关system time slice,并重置task的time slice为预设值,将sp更新为system_stack_pointer,调用schedule(),调度的过程是非常简单的查询,就是查看两个全局的变量,TCD_Execute_HISR和TCD_Execute_Task,schedule部分的关键是打开了中断,不然如果当前没有ready的task或是被激活的HISR,则shedule死循环下去,查询到下一个应该执行的线程后跳转至control_to_thread(),在这里重新开启system time slice,然后将线程的tc_stack_ptr加入到sp中,切换至线程的栈中,依次pop出来,即完成了任务调度。

任务的切换主要是上下文的切换,也就是task栈的切换,函数的调用会保存部分regs和返回地址,这些动作都是编译器来完成的,而OS中的任务切换是运行时(runtime)的一种状态变化,因此编译器也无能为力,所以对于上下文的保存需要代码来实现。

任务的抢占是异步的因此必须要通过中断来实现,一般每次timer的中断决定当前的task的slice time是否expired,然后设置TCT_Set_Execute_Task为相同优先级的其他task或更高优先级的task;高优先级的task抢占低优先级的task,一般是外部中断触发,在HISR中resume_task()唤醒高优先级的task,然后schedule到高优先级的task中,因为timer的HISR是在系统初始化就已经注册的,只是执行timeout和time slice超时后的操作,并没有执行resume_task的动作。

NU中的stack有两种solicited stack和interrupt stack,solicited stack是一种minmum stack,而interrupt stack是对当前所有寄存器全部保存,TCB中的minimum stack size = 申请得到stack size - solicited stack(在arm mode下占44字节,thumb mode下占48字节),thumb标志用来记录上下文保存时的ARM的工作模式,c代码编译为thumb模式,这样可以减小code size,提高代码密度,assembly代码编译为arm模式提升代码的效率,NU中内核的代码不多,主要是assembly代码。stack的类型与其中PC指向的shell无关,interrupt stack保存的是task或是HISR在执行的过程中被中断时的现场,solicited stack建立的地方包括 control_to_system()、schedule_protect()和send_signals()发送给占有protect资源的task的情况,HISR_Shell()执行完后会建立solicited stack,再跳转至schedule。

(Lower Address) Stack Top -> 1 (Interrupt stack type) CPSR Saved CPSR r0 Saved r0 r1 Saved r1 r2 Saved r2 r3 Saved r3 r4 Saved r4 r5 Saved r5 r6 Saved r6 r7 Saved r7 r8 Saved r8 r9 Saved r9 r10 Saved r10 r11 Saved r11 r12 Saved r12 sp Saved sp lr Saved lr (Higher Address) Stack Bottom-> pc Saved pc

(Lower Address) Stack Top -> 0 (Solicited stack type) !!FOR THUMB ONLY!! 0/0x20 Saved state mask r4 Saved r4 r5 Saved r5 r6 Saved r6 r7 Saved r7 r8 Saved r8 r9 Saved r9 r10 Saved r10 r11 Saved r11 r12 Saved r12 (Higher Address) Stack Bottom-> pc Saved pc 一个简单的例子说明stack的情况,首先是一个task在ready(executing)的状态下,而且time slice超时了,timer中断发生后,保存task上下文interrupt_contex_save(),在task的tc_stack_ptr指向的地方建立中断栈 taskA |interrupt stack|___tc_stack_ptr 栈顶端是pc=lr-4 ARM对于中断的判定发生在当前指令完成execute时,同时pipeline的原因pc=pc+8,入栈时就把lr-4首先放在stack的最高端(high)。

timer的LISR完成后激活了HISR,执行TCC_Time_slice()将当前task移到相同优先级的尾端,并且设置下一个要执行的task,HISR在栈顶端保存的是这个HISR_shell的入口地址,因为task的执行完就finished,HISR是可重入的 HISR |solicited stack| 栈顶端是HISR_shell_entry

中断(interrupt)

前面已经提及了中断的基本操作,这里就写一些代码路径的细节,中断的执行主要是两个部分LISR和HISR,分成两个部分的目的就是将关中断的时间最小化,并且在LISR中开中断允许中断的嵌套,以及建立中断优先级,都可以减少中断的延迟,保证OS的实时性。 NU的中断模式是可重入的中断处理方式,也就是基于中断优先级和嵌套的模式,中断的嵌套在处理的过程中应对lr_irq_mode寄存器进行保存,因为高优先级的中断发生时会覆盖掉低优先级中断的r14和spsr,因此要利用系统的栈来保存中断栈。

NU对于中断上下文的保存具体操作如下: (1)在中断发生后执行的入口函数INT_IRQ()中,将r0-r4保存至irq的栈中 (2)查找到对应的interrupt_shell(),clear中断源,更新全局的中断计数器,然后进行interrupt_contex_save (3)首先利用r1,r2,r3保存irq模式下的sp,lr,spsr,这里sp是用来切换至系统栈后拷贝lr和spsr的,这里保存lr和spsr是目的是task被抢占后,当再次schedule时可以返回task之前的状态。 (4)切换至SVC模式,如果是非嵌套的中断则保存上下文至task stack中,将irq模式下的lr作为顶端PC的返回值入栈,将SVC模式下的r6-r14入栈,将irq模式下的sp保存至r4中入栈,最后将保存在irq_stack中的r0-r4入栈 (5)如果是嵌套中断,中断的嵌套发生在LISR中,在执行LISR时已经切换至system stack,因此嵌套中断要将中断的上下文保存至system stack中,与task stack中interrupt stack相比只是少了栈顶用来标记嵌套的标志(1 not nested) (6)有一个分支判断,就是如果当前线程是空,即TCD_Current_Thread == NULL,表明当前是schedule中,因为初始化线程是关中断的,这样就不为schedule线程建立栈帧,因为schedule不需要保存上下文,在restore中断上下文时直接跳转至schedule。

中断上下文的恢复 全局的中断计数器INT_Count是否为0来判定当前出栈的信息,如果是嵌套则返回LISR中,否则切换至system stack执行schedule

timer timer与中断紧密相关,其实timer也是中断的一种,只是发生中断的频率较高,且作用重大,一个实时操作系统,时间是非常重要的一部分,NU中的timer主要有四个作用: (1)维护系统时钟 TMD_system_clock (2)task的time slice (3)task的suspend timeout timer (4)application timer 其中(3)(4)共用一种机制,一个全局的时间轴TMD_timer,timeout timer和app timer都建立在一个TM_TCB的数据结构上,通过tm_remaining_time来表征当前timer的剩余时间,例如当前有timer_list上有三个TM_TCB,依次是Ta = 5,Tb = 7, Tc = 20,那么建立的链表上剩余时间依次是5,2,8,如果现在要加入一个新的timer根据timer值插入至合适的位置,如果插入的timer为13,则安排在Tb后面,剩余时间为1,后面的8改为7,当发生了timer expired,则触发timer_HISR,如果是app timer则执行timer callback,如果是task timeout timer,则执行TCC_Task_Timeout唤醒task。

(2)的实现也是依赖于全局的time slice时间轴,每一个task在执行时都会将自己的时间片信息更新至全局的时间轴上,当一个task的time slice执行完在timer HISR中调用TCC_task_Timeout将当前的task放在相同优先级list的最尾端,并设置下一个最高优先级的任务。task在执行的过程中只有被中断后time slice会保存下来,其他让出处理器的情况都会将time slice更新为预设值。

protect protect与linux的锁机制类似,互斥访问,利用开关中断来实现,并且拥有protect的task是不可以suspend的,必须要将protect释放后才可以挂起,当一个优先级较低的task占有protect资源,如果被抢占,一个高优先级的task或HISR在请求protect资源时会执行TCC_schedule_protect()让出处理器给低优先级的task执行,直到低优先级的task执行unprotect()为止,此时task或HISR建立的是solicited stack,同时在control_to_thread前开关中断一次,这样可以减少一次上下文的切换。NU中常用到的是system_protect,它就是一把大锁,保护内核中所有全局数据结构的顺序访问,粒度很大。

LISR中不可以请求protect资源,因为LISR是中断task后执行,如果task占有protect资源,这时LISR又去请求protect资源,会发生死锁,因为LISR让出处理器后,schedule没办法再次调度到LISR执行,则发生死锁错误,因此在LISR中除了activate_HISR()以外不可以使用system call,例如resume_task等等,这写系统调用都会请求protect资源。

对于protect的请求按照一定的顺序可以防止死锁,NU的源码中一般将system_protect资源的请求放在后面,其他如DM_protect先请求。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

为了满足日益增长的数据处理需求,铁威马NAS推出了全新的性能巅峰2024年旗舰之作F4-424 Pro,并搭载了最新的操作系统--TOS 6。这款高效办公神器的问世,无疑将为企业和专业人士带来前所未有的便捷与效率。

关键字: 存储 Linux 服务器

Arm CPU正在从根本上推动AI变革,并造福地球。Arm架构是未来AI计算的基石。​

关键字: ARM AI

近日,Arm推出了Arm® Ethos™-U85神经网络处理器(NPU)和Arm Corstone™-320物联网参考设计平台,旨在满足海量的数据处理和大规模计算,加速推进边缘AI的发展进程。

关键字: ARM

为了赶超云计算市场上的竞争对手,谷歌正试图通过定制的Arm服务器芯片降低云计算服务成本。

关键字: 谷歌 ARM 定制芯片

嵌入式开发作为一个融合了计算机软硬件和系统工程的综合性领域,其成功与否往往取决于三个核心要素的有效整合与协调。这三个要素分别是:硬件平台的选择与设计、软件开发及其优化、以及系统级的设计与集成。深入理解并熟练掌握这三个方面...

关键字: 嵌入式开发 ARM

随着汽车软件数量爆发式的增长,整个行业都需要重新思考汽车产品的开发流程。为此,Arm推出了丰富的硬件IP、新的系统IP,以及全新的汽车计算与计算子系统产品路线图,旨在为各种汽车应用实现性能、功能安全、可扩展等方面的支持。

关键字: ARM 汽车电子

双系统将是下述内容的主要介绍对象,通过这篇文章,小编希望大家可以对双系统的相关情况以及信息有所认识和了解,详细内容如下。

关键字: 双系统 Windows Linux

知名移动芯片设计公司ARM最近迈出重要一步,它正式推出汽车芯片设计。ARM推出的芯片设计方案名叫Neoverse,随同芯片一起推出的还有面向汽车制造商、汽车供应商的新系统。

关键字: ARM 汽车芯片 芯片

随着通用人工智能的发展,数据中心的计算需求逐步提高。针对多模态数据、大模型的推理和训练需要更高的算力支持,而随着算力提升与之而来的还需更关注在功耗方面的优化。对于头部云计算和服务厂商而言,针对专门用例提高每瓦性能变得至关...

关键字: ARM 服务器 AI Neoverse CSS

一直以来,riscv架构都是大家的关注焦点之一。因此针对大家的兴趣点所在,小编将为大家带来riscv架构的相关介绍,详细内容请看下文。

关键字: riscv ARM riscv架构
关闭
关闭