Liteos-a内核分析(二)

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2020年12月19日19:50:30 评论 1,100

系统初始化

这里只粗略的分析下main函数做了哪些事情,目的是了解内核初始化的基本流程。main函数在kernel\liteos_a\platform\main.c中。

1. 设置系统主任务

在liteos_a内核中,使用LosTaskCB数据结构表示任务控制块,g_mainTask这个任务控制块是用于后面创建内核idle进程时初始化idle进程的主线程的任务控制块所用。

通过下图可以看出,g_mainTask只初始化了任务状态、优先级等等。OS_TASK_PRIORITY_LOWEST的值为31,由此可见liteos_a任务默认有32个优先级,0优先级最高,31优先级最低。同时从图中还可以看出每个核都有一个g_mainTask。

Liteos-a内核分析(二)

初始化后的g_mainTask这个任务TCB地址被保存在arm的tpidrprw寄存器中,tpidrprw寄存器被arm设计用于保存当前运行线程的ID值,这里这个寄存器被liteos_a用于保存当前运行线程的TCB地址:

Liteos-a内核分析(二)

2. 输出系统信息

接着调用OsSystemInfo输出系统的相关信息:

Liteos-a内核分析(二)

从图中可以看出,这里会调用HalIrqVersion来获得当前中断控制器的型号,对于cortex-a7而言,使用的是gic_v2中断控制器,这个函数的实现在kernel\liteos_a\platform\hw\arm\interrupt\gic\gic_v2.c中:

Liteos-a内核分析(二)

3. 系统tick初始化

接着初始化系统的tick值和用于产生tick的定时器的工作时钟频率:

Liteos-a内核分析(二)

OS_SYS_CLOCK是在板级配置文件platform_config.h中定义的,表示定时器的工作频率:

Liteos-a内核分析(二)

LOSCFG_BASE_CORE_TICK_PER_SECOND如果在platform_config.h中没有定义的话,默认值是100,表示系统10ms产生一次tick中断:

Liteos-a内核分析(二)

这里要记录定时器的工作频率是为了系统进行更精确的延时时计算时间使用,比如us级别的延时。

4. 中断初始化

调用OsHwiInit初始化内核中断管理结构和硬件中断控制器:

Liteos-a内核分析(二)

OS_HWI_MAX_NUM的默认值为128,可见liteos_a默认最大可用的中断数是128个,这个宏定义在hal_platform_ints.h中,这个头文件也是板级相关的配置文件:

Liteos-a内核分析(二)

内核使用g_hwiForm数组来保存每个中断号对应的中断处理函数的地址,这样在发生中断时,可以根据中断号从数组中找到对应的中断处理函数。

调用HalIrqInit函数来初始化硬件中断控制器:

Liteos-a内核分析(二)

5.初始化tick硬件定时器

在函数OsTickInit中,通过调用HalClockInit函数来初始化硬件定时器:

Liteos-a内核分析(二)

在arm体系架构中,有两类定时器是芯片厂商必须实现的,一个是私有定时器,这个是每个cpu核私有的定时器,只有本cpu核可以访问;另一个是全局定时器,这是在cpu核之外的,所有核都可以访问。另外每个SOC厂商可能还会使用第三方定时器,这样在整个SOC系统中,就会有3种定时器,一般使用全局定时器或者第三方定时器作为系统tick定时器。

Liteos-a内核中实现的cortex-a7的私有和全局定时器驱动代码在kernel\liteos_a\platform\hw\arm\timer目录下:

Liteos-a内核分析(二)

6.任务管理数据结构初始化

调用OsTaskInit初始化内核TCB池和进程优先级表:

Liteos-a内核分析(二)

内核使用LOSCFG_BASE_CORE_TSK_LIMIT表示TCB的最大个数,默认值为128,表示内核最多只能有128个线程:

Liteos-a内核分析(二)

内核使用g_taskCBArray数组表示内核的TCB池,初始化完TCB池后,使用OsPriQueueInit函数来初始化进程优先级表:

Liteos-a内核分析(二)

那么进程优先级表是干嘛用的呢,在liteos_a的设计中,不仅仅线程是有优先级之分的,进程也是有优先级之分的,进程优先级也是只有32个级别,0最高,31最低。每个进程都是用进程控制块PCB来表示的。

g_priQueueList用于将同等优先级的进程链在一起形成链表:

Liteos-a内核分析(二)

同时内核还使用一个g_priQueueBitmap来表示哪个优先级下有进程就绪,g_priQueueBitmap是一个32位的变量,正好可以用来表示32个优先级,但是要注意,bit0表示优先级31,bit31表示优先级0,之所以这么设计是为了使用硬件CLZ指令来快速确定在所有准备就绪的进程中,最高优先级是哪个。

7. 进程间通信初始化

调用OsIpcInit函数来初始化内核进程间通信,比如信号量和消息队列:

Liteos-a内核分析(二)

8. 系统内存管理初始化

调用OsSysMemInit函数来初始化内核跟内存管理相关的东西:

Liteos-a内核分析(二)

这个函数比较重要,会在下一篇文章中较详细的分析,这里只要知道这个函数主要初始化了内核堆空间、虚拟地址和物理地址空间,并且使用了二级页表,最后进行共享内存的初始化。

9. 系统调用初始化

调用SyscallHandleInit函数初始化了系统调用相关数据结构:

Liteos-a内核分析(二)

Liteos-a内核设计最大的系统调用数大概是500个左右,当前实际可用的肯定小于500,g_syscallHandle数组用来记录各个系统调用号对应的系统调用执行函数。

10. 内核进程初始化

10.1 KProcess进程初始化

调用OsKernelInitProcess来初始化内核进程相关东西:

Liteos-a内核分析(二)

首先调用OsProcessInit函数初始化内核进程池:

Liteos-a内核分析(二)

内核使用LosProcessCB表示一个进程控制块,g_processCBArray表示内核的进程控制块池,LOSCFG_BASE_CORE_PROCESS_LIMIT表示最大的进程数,默认值为64,表示最多有64个进程。

从上图中还可以看出,用户init进程的ID固定为1,内核init进程的ID固定为2。

接着调用OsProcessCreateInit创建内核KProcess进程:

Liteos-a内核分析(二)

可以看出,这个进程的优先级是0,表示是最高优先级。OsProcessCreateInit对进程PCB进行了初始化:

Liteos-a内核分析(二)

我们这里只关注OsInitPCB函数,这个函数首先将进程的优先级和调度方式记录在PCB中,然后有一个比较重要的操作就是初始化了当前进程的虚拟空间控制块:

Liteos-a内核分析(二)

每个进程都有一个LosVmSpace虚拟空间控制块,其地址记录在PCB中的vmSpace成员中,用于表示当前进程的虚拟空间布局情况,从上图中可以看出,如果是内核进程,直接使用LOS_GetKVmSpace获得内核的虚拟空间控制块,并记录在PCB控制块中。

然后将当前cpu运行的进程设置为KProcess进程:

Liteos-a内核分析(二)

内核使用数组g_runProcess表示每个cpu核当前运行的进程PCB:

Liteos-a内核分析(二)

10.2 ResourcesTask线程初始化

调用OsCreateIdleProcess创建并初始化内核idle进程:

Liteos-a内核分析(二)

首先使用OsCreateResourceFreeTask创建了一个ResourcesTask线程,线程优先级为5:

Liteos-a内核分析(二)

从图中可以看出,首先初始化了一个线程参数结构体,主要是记录了线程的执行函数和线程优先级还有线程栈大小,线程栈大小默认为4KB。下面我们来重点分析LOS_TaskCreate函数。

首先记录了当前线程是属于哪个进程的,然后调用LOS_TaskCreateOnly初始化线程控制块TCB,最后使用OS_TASK_SCHED_QUEUE_ENQUEUE设置进程优先级表和当前线程所属进程的线程优先级表:

Liteos-a内核分析(二)

我们先来看看LOS_TaskCreateOnly函数,首先申请了一个空闲的TCB控制块,接着申请了一块内存空间当做栈:

Liteos-a内核分析(二)

然后使用OsTaskStackInit初始化了当前线程的栈,这个函数初始化了栈中寄存器上下文,当线程被调度运行时,就可以从栈中恢复这些寄存器,从而恢复线程的运行。这些上下文寄存器中最重要的就是pc寄存器,这个pc寄存器在栈中被初始化为OsTaskEntry这个函数地址:

Liteos-a内核分析(二)

这个函数是所有线程第一次运行时的入口函数,我们看看这个函数干了些什么:

Liteos-a内核分析(二)

这个函数主要就是调用taskCB->taskEntry,这里保存的就是我们创建线程时传入的自定义函数的地址。

我们回过来看看OsTaskCBInit函数,这个函数首先调用OsTaskCBInitBase函数:

Liteos-a内核分析(二)

OsTaskCBInitBase中初始化了线程TCB,记录了线程的优先级和线程自定义执行函数:

Liteos-a内核分析(二)

初始化完线程TCB后,LOS_TaskCreate调用了OS_TASK_SCHED_QUEUE_ENQUEUE,让我们来看看这个宏做了啥。

这个宏是函数OsTaskSchedQueueEnqueue的重命名,来看看这个函数:

Liteos-a内核分析(二)

可以看出这个函数就是把线程所在的进程加入到进程就绪表中,我们来看下OS_PROCESS_PRI_QUEUE_ENQUEUE这个宏:

Liteos-a内核分析(二)

看下OsPriQueueEnqueue这个函数:

Liteos-a内核分析(二)

首先根据当前优先级将g_priQueueBitmap中对应的位置1,表示有线程准备就绪,然后将当前进程放入g_priQueueList对应优先级的链表中。

回到OsTaskSchedQueueEnqueue函数中来,将进程放入进程就绪表后,接着调用OsSchedTaskEnqueue将线程放入当前进程PCB的线程就绪表中:

Liteos-a内核分析(二)

我们来看看OS_TASK_PRI_QUEUE_ENQUEUE这个宏:

Liteos-a内核分析(二)

可以看出,这个宏同样调用了OsPriQueueEnqueue这个函数,只是传入的参数分别是进程PCB中的线程优先级表和就绪线程bitmap信息。

10.3 KIdle进程初始化

内核使用LOS_Fork函数创建并初始化idle进程:

Liteos-a内核分析(二)

这个函数调用了OsCopyProcess来初始化进程相关资源:

Liteos-a内核分析(二)

首先申请一个空闲的PCB控制块,然后初始化并复制父进程的内核资源,我们来看看OsForkInitPCB这个函数:

Liteos-a内核分析(二)

首先调用了OsInitPCB来初始化PCB,然后使用OsCopyTask来初始化子进程的主线程:

Liteos-a内核分析(二)

我们来看看OsInitCopyTaskParam函数,这个函数主要记录了主线程的自定义执行函数、并继承当前运行线程的优先级、调度方式等信息:

Liteos-a内核分析(二)

在系统初始化过程中,当前的运行线程就是一开始设置的那个g_mainTask临时线程。

创建好idle进程后,将idle进程ID记录在g_kernelIdleProcess中:

Liteos-a内核分析(二)

11. 创建系统初始化线程

调用OsSystemInit创建SystemInit线程用于执行驱动相关初始化:

Liteos-a内核分析(二)

OsSystemInitTaskCreate创建了一个SystemInit线程,线程执行函数名字就是SystemInit:

Liteos-a内核分析(二)

这个SystemInit函数是每个板级需要实现的,这里我们来看下hi3516dv300板子的SystemInit中做了哪些事情:

Liteos-a内核分析(二)

可以看出主要就是系统内核一些组件的初始化还有硬件驱动的初始化,如果是一个新的板子,可以参考这个SystemInit初始化流程。

12. 其他内核功能初始化

接下来就是内核其他功能初始化:

Liteos-a内核分析(二)

13. 启动内核

调用OsStart启动内核,选取一个优先级最高的线程调度运行:

Liteos-a内核分析(二)

首先调用OsTickStart启动系统tick定时器,这个函数就是调用HalClockStart来启动具体的硬件定时器:

Liteos-a内核分析(二)

接着调用OsGetTopTask函数找到优先级最高的线程:

Liteos-a内核分析(二)

我们来看看OsGetTopTask这个函数:

Liteos-a内核分析(二)

这个函数首先找到就绪的优先级最高的进程,然后再找出这个进程中就绪的优先级最高的线程,最后返回这个优先级最高的线程的TCB。注意这里使用了CLZ硬件指令在进程bitmap和线程bitmap中寻找就绪的最高优先级,CLZ指令的含义是从最高位开始到第一个为1的位之间的0的个数,其实这个值就是优先级值,所以现在明白为什么0~31优先级和32位变量的bit0~bit31是反着对应的关系了吧。

选出最高优先级的线程后,调用OsStartToRun函数运行这个线程,我们看下OsStartToRun函数的实现:
Liteos-a内核分析(二)

首先将线程的状态改为OS_TASK_STATUS_RUNNING:

Liteos-a内核分析(二)

接着将当前线程的TCB地址记录在在arm的tpidrprw寄存器中,这个寄存器之前一直保存的是g_mainTask这个临时线程TCB地址,现在终于换成了真正要运行的线程:

Liteos-a内核分析(二)

然后调用OsTaskContextLoad从要运行的线程的栈中恢复上下文信息:

Liteos-a内核分析(二)

这个函数首先判断当前是用户态还是内核态环境,如果是内核态,调用OsKernelTaskLoad:

Liteos-a内核分析(二)

首先恢复了r0~r12寄存器值,然后恢复LR和PC寄存器的值,这个时候cpu就跳转到OsTaskEntry这个函数执行了。

gewenbin
  • 本文由 发表于 2020年12月19日19:50:30
  • 转载请务必保留本文链接:http://www.databusworld.cn/9090.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: