Tips: This article does not describe the content associated with the floating-point registers, learn self-inspection (after all, I do not know)
The basic concept of the scheduler
TencentOS tiny
It provided the task scheduler is priority-based preemptive scheduling full, the system is running, when there are more ready than the current task priority of the task, the current task immediately 切出
, high-priority task 抢占
processor is running.
TencentOS tiny
The kernel also allows you to create the same priority task. Priority tasks using the same round-robin scheduling mode (which is often said time-sharing scheduling), a round-robin scheduling is current systems no higher-priority ready task to be effective in the case.
In order to ensure real-time system, the system maximum extent possible to ensure that high-priority task to run. Principle task scheduling is once the task status is changed, and the currently running task priority is less than the priority tasks in the queue the highest priority, immediately task switching (unless the current system is in state interrupt handler or disable task switch) .
The scheduler is the operating system 核心
, its main function is 实现任务的切换
that the ready list from which 找到
the highest priority task, then go to 执行
the task.
Start Scheduler
The scheduler is initiated by cpu_sched_start
to complete the function, it will be tos_knl_start
invoked function that mainly do two things, first by readyqueue_highest_ready_task_get
acquiring the current system function at the highest priority ready task and assign it to point to the current task control block pointer k_curr_task
, then set the state of the system is about the operating mode KNL_STATE_RUNNING
.
Of course, the most important thing is to call functions written in assembly code cpu_sched_start
to start the scheduler, the function in the source arch\arm\arm-v7m
directory port_s.S
under assembly files, TencentOS tiny
support for multiple core chip, as M3/M4/M7
other different ways to achieve different chips that function, port_s.S
but also TencentOS tiny
as a software connected with the CPU hardware 桥梁
. M4 is to cpu_sched_start
give an example:
__API__ k_err_t tos_knl_start(void)
{
if (tos_knl_is_running()) {
return K_ERR_KNL_RUNNING;
}
k_next_task = readyqueue_highest_ready_task_get();
k_curr_task = k_next_task;
k_knl_state = KNL_STATE_RUNNING;
cpu_sched_start();
return K_ERR_NONE;
}
port_sched_start
CPSID I
; set pendsv priority lowest
; otherwise trigger pendsv in port_irq_context_switch will cause a context swich in irq
; that would be a disaster
MOV32 R0, NVIC_SYSPRI14
MOV32 R1, NVIC_PENDSV_PRI
STRB R1, [R0]
LDR R0, =SCB_VTOR
LDR R0, [R0]
LDR R0, [R0]
MSR MSP, R0
; k_curr_task = k_next_task
MOV32 R0, k_curr_task
MOV32 R1, k_next_task
LDR R2, [R1]
STR R2, [R0]
; sp = k_next_task->sp
LDR R0, [R2]
; PSP = sp
MSR PSP, R0
; using PSP
MRS R0, CONTROL
ORR R0, R0, #2
MSR CONTROL, R0
ISB
; restore r4-11 from new process stack
LDMFD SP!, {R4 - R11}
IF {FPU} != "SoftVFP"
; ignore EXC_RETURN the first switch
LDMFD SP!, {R0}
ENDIF
; restore r0, r3
LDMFD SP!, {R0 - R3}
; load R12 and LR
LDMFD SP!, {R12, LR}
; load PC and discard xPSR
LDMFD SP!, {R1, R2}
CPSIE I
BX R1
Cortex-M core off interrupt instruction
From the above assembly code, I want to introduce the Cortex-M
kernel interrupt disable instruction, alas ~ feel a little trouble!
To quickly switch interrupt, Cortex-M specifically set up a core CPS 指令
for operating the PRIMASK
register with the FAULTMASK
register, the two registers are maskable interrupt associated with, in addition to Cortex-M
the core there is BASEPRI
a register associated with the interrupt it is also incidentally tell us about it.
CPSID I ;PRIMASK=1 ;关中断
CPSIE I ;PRIMASK=0 ;开中断
CPSID F ;FAULTMASK=1 ;关异常
CPSIE F ;FAULTMASK=0 ;开异常
register | Features |
---|---|
PRIMASK | 1 after it has been set, turn off all maskable exception, only in response NMI and HardFault FAULT |
FAULTMASK | When it is set to 1, only NMI can, all the other exceptions are unable to respond (including HardFault FAULT) |
BASEPRI | This register up to nine (the number of bits expressing priority decision). It defines a priority masked threshold. When it is set to a value, all numbers greater than or equal to this priority interrupts are turned value (the greater the priority number, the lower the priority). However, if is set to 0, no interrupt is not turned off |
More detailed description see my previous article: RTOS critical sections of knowledge: https: //blog.csdn.net/jiejiemcu/article/details/82534974
Return to the topic
In the kernel scheduler to start the process you need to configure PendSV
the interrupt priority level is the lowest, that is, to NVIC_SYSPRI14(0xE000ED22)
write the address NVIC_PENDSV_PRI(0xFF)
. Because PendSV
will involve system scheduling, system scheduling priority to 低于
other hardware system interrupt priority, that priority response system of external hardware interrupts, so PendSV interrupt priority To configure the lowest, or is likely in interrupt context is generated task scheduling.
PendSV
Abnormal context switch will automatically delay the request until the other ISR
have completed treatment after release. To achieve this mechanism, we need to PendSV
be programmed to the lowest priority exception. If OS
detected a ISR
being active, it hanging from an PendSV
exception, in order to perform a context switch suspended. In other words, as long as the PendSV
priority to the lowest, systick even interrupted IRQ, it will not be a context switch immediately, but wait until ISR
executed, PendSV
service routine began to perform, and perform a context switch on the inside . As shown in FIG procedure:
then you obtain MSP
the address of the primary stack pointer, in the Cortex-M
middle, 0xE000ED08
a SCB_VTOR
register address, which is stored the start address of the vector table.
Load k_next_task
pointed to the task control block R2
, the first known member of the task control block is the stack pointer from the previous article, so in this case R2
equal to the stack pointer.
ps: When the scheduler starts,
k_next_task
andk_curr_task
is the same as (k_curr_task = k_next_task
)
Loaded R2
into R0
, then the stack pointer R0
updates to psp
the stack pointer is used when the task execution is psp
.
ps:
sp
Pointer two, respectivelypsp
andmsp
. (Can be simply understood as: use in mission contextspsp
, the use of the interrupt contextmsp
, not necessarily correct, this is my personal understanding)
In R0
the base address, the stack grows upwards in the 8
loading of the contents of CPU registers words R4~R11
, while R0
will follow the self-energizing
Then you need to load R0 ~ R3、R12以及LR、 PC、xPSR
into the CPU register sets, PC is a pointer to the thread is about to run, while the LR register exit points to the task. 因为这是第一次启动任务,要全部手动把任务栈上的寄存器弹到硬件里,才能进入第一个任务的上下文,因为一开始并没有第一个任务运行的上下文环境,而在进入PendSV的时候需要上文保存,所以需要手动创造任务上下文环境(将这些寄存器加载到CPU寄存器组中)
First assembler when the entry function, sp is the top of the stack of the task points to a selected ( k_curr_task
).
Take a look at the task stack initialization
From the above understanding, look at the stack initialization tasks, there may be a little deeper impression. Mainly to understand the following points:
- Gets the stack pointer
stk_base[stk_size]
high address,Cortex-M
the kernel stack is向下增长
in. R0、R1、R2、R3、R12、R14、R15和xPSR的位24
CPU will be自动
loaded and saved.- xPSR of
bit24必须置1
that 0x01000000. - entry is the entry address the task, namely
PC
- R14 (
LR
) is the exit address the task, so the task is generally an infinite loop withoutreturn
- R0: arg parameter is the main task
- Initializing the stack pointer sp is decremented
__KERNEL__ k_stack_t *cpu_task_stk_init(void *entry,
void *arg,
void *exit,
k_stack_t *stk_base,
size_t stk_size)
{
cpu_data_t *sp;
sp = (cpu_data_t *)&stk_base[stk_size];
sp = (cpu_data_t *)((cpu_addr_t)(sp) & 0xFFFFFFF8);
/* auto-saved on exception(pendSV) by hardware */
*--sp = (cpu_data_t)0x01000000u; /* xPSR */
*--sp = (cpu_data_t)entry; /* entry */
*--sp = (cpu_data_t)exit; /* R14 (LR) */
*--sp = (cpu_data_t)0x12121212u; /* R12 */
*--sp = (cpu_data_t)0x03030303u; /* R3 */
*--sp = (cpu_data_t)0x02020202u; /* R2 */
*--sp = (cpu_data_t)0x01010101u; /* R1 */
*--sp = (cpu_data_t)arg; /* R0: arg */
/* Remaining registers saved on process stack */
/* EXC_RETURN = 0xFFFFFFFDL
Initial state: Thread mode + non-floating-point state + PSP
31 - 28 : EXC_RETURN flag, 0xF
27 - 5 : reserved, 0xFFFFFE
4 : 1, basic stack frame; 0, extended stack frame
3 : 1, return to Thread mode; 0, return to Handler mode
2 : 1, return to PSP; 0, return to MSP
1 : reserved, 0
0 : reserved, 1
*/
#if defined (TOS_CFG_CPU_ARM_FPU_EN) && (TOS_CFG_CPU_ARM_FPU_EN == 1U)
*--sp = (cpu_data_t)0xFFFFFFFDL;
#endif
*--sp = (cpu_data_t)0x11111111u; /* R11 */
*--sp = (cpu_data_t)0x10101010u; /* R10 */
*--sp = (cpu_data_t)0x09090909u; /* R9 */
*--sp = (cpu_data_t)0x08080808u; /* R8 */
*--sp = (cpu_data_t)0x07070707u; /* R7 */
*--sp = (cpu_data_t)0x06060606u; /* R6 */
*--sp = (cpu_data_t)0x05050505u; /* R5 */
*--sp = (cpu_data_t)0x04040404u; /* R4 */
return (k_stack_t *)sp;
}
Find the highest priority task
If only one operating system has a high-priority task can 立即
get processors and with the characteristics of the implementation, then it's still not a real-time operating system. Because of this finds the highest priority task scheduling process determines whether the time is deterministic, it can simply be used 时间复杂度
to describe it, if the system finds the highest priority task of the time O(N)
, so this time vary with the number of tasks increases increases, which is not desirable, TencentOS tiny
the time complexity is O(1)
that it provides two ways to find the highest priority task, by TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT
definition determines the macro.
- The first common method is to use, according to the ready list
k_rdyq.prio_mask[]
is determined whether the corresponding bit is set variable. - The second method is a special method, using the calculated leading zero instructions
CLZ
directly ink_rdyq.prio_mask[]
the32
drawn position of the highest priority bits in which the variables directly, this method is more efficient than the conventional method,但受限于平台
(leading zero instructions requires hardware, in STM32, we can use this method).
Implementation process follows, it is recommended to see readyqueue_prio_highest_get
the function, his realization is still very delicate ~
__STATIC__ k_prio_t readyqueue_prio_highest_get(void)
{
uint32_t *tbl;
k_prio_t prio;
prio = 0;
tbl = &k_rdyq.prio_mask[0];
while (*tbl == 0) {
prio += K_PRIO_TBL_SLOT_SIZE;
++tbl;
}
prio += tos_cpu_clz(*tbl);
return prio;
}
__API__ uint32_t tos_cpu_clz(uint32_t val)
{
#if defined(TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT) && (TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT == 0u)
uint32_t nbr_lead_zeros = 0;
if (!(val & 0XFFFF0000)) {
val <<= 16;
nbr_lead_zeros += 16;
}
if (!(val & 0XFF000000)) {
val <<= 8;
nbr_lead_zeros += 8;
}
if (!(val & 0XF0000000)) {
val <<= 4;
nbr_lead_zeros += 4;
}
if (!(val & 0XC0000000)) {
val <<= 2;
nbr_lead_zeros += 2;
}
if (!(val & 0X80000000)) {
nbr_lead_zeros += 1;
}
if (!val) {
nbr_lead_zeros += 1;
}
return (nbr_lead_zeros);
#else
return port_clz(val);
#endif
}
Realization of task switching
We also know from the front, task switching is PendSV
carried out in the interrupt, the interrupt is implemented as a summary of the content essence of the word is above save, switch below , direct look at the source code:
PendSV_Handler
CPSID I
MRS R0, PSP
_context_save
; R0-R3, R12, LR, PC, xPSR is saved automatically here
IF {FPU} != "SoftVFP"
; is it extended frame?
TST LR, #0x10
IT EQ
VSTMDBEQ R0!, {S16 - S31}
; S0 - S16, FPSCR saved automatically here
; save EXC_RETURN
STMFD R0!, {LR}
ENDIF
; save remaining regs r4-11 on process stack
STMFD R0!, {R4 - R11}
; k_curr_task->sp = PSP
MOV32 R5, k_curr_task
LDR R6, [R5]
; R0 is SP of process being switched out
STR R0, [R6]
_context_restore
; k_curr_task = k_next_task
MOV32 R1, k_next_task
LDR R2, [R1]
STR R2, [R5]
; R0 = k_next_task->sp
LDR R0, [R2]
; restore R4 - R11
LDMFD R0!, {R4 - R11}
IF {FPU} != "SoftVFP"
; restore EXC_RETURN
LDMFD R0!, {LR}
; is it extended frame?
TST LR, #0x10
IT EQ
VLDMIAEQ R0!, {S16 - S31}
ENDIF
; Load PSP with new process SP
MSR PSP, R0
CPSIE I
; R0-R3, R12, LR, PC, xPSR restored automatically here
; S0 - S16, FPSCR restored automatically here if FPCA = 1
BX LR
ALIGN
END
The PSP
value stored R0
. When entering PendSVC_Handler
, the operating environment on a task that is: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0
the CPU registers will be 自动
stored to the job stack, in which case the pointer has been updated automatically psp. While the rest r4~r11
need 手动
to save, and this is why you want to PendSVC_Handler
save the above ( _context_save
) of the main reasons for not automatically load the saved CPU registers, which was pressed into the task stack.
Then find a task to be run next k_next_task
, it is loaded into the stack of the task R0
, the new task and then manually stack contents (herein means R4~R11
) is loaded into the CPU
register set, this is the context switch, of course, there are other not method content is automatically saved manually loaded into the CPU
register bank. After loading manual, this time R0
has been updated, the updated value of the psp, exit PendSVC_Handler
interrupt, will be psp
used as the base address, the task stack the rest of the content ( xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0
) is automatically loaded into the CPU registers.
In fact, when an exception occurs, R14 saved abnormal return flag, including the return after entering the task mode or processor mode, use the PSP stack pointer or stack pointer MSP. In this case equals r14 0xFFFFFFFD, a rear most task back into the abnormal mode (after PendSVC_Handler
the priority is the lowest, will return to the task), SP PSP as a stack pointer to the stack, the stack after completion PSP
points to the top of the stack task. When the call instruction BX R14, the system PSP
as SP
a stack pointer, stack task to the new task to run next is loaded into the remaining contents of CPU registers: R0、R1、R2、R3、R12、R14(LR)、R15(PC)和xPSR
to switch to a new task.
SysTick
SysTick initialization
systick is based system, and it is the core clock, as long as M0/M3/M4/M7
the core it will present systick
a clock, and it is programmed to be configured, which provides great convenience for the operating system migration.
TencentOS tiny
Will cpu_init
in the function systick
is initialized, i.e., it calls cpu_systick_init
the function, this way there is no need to write user- systick
initialization related code.
__KERNEL__ void cpu_init(void)
{
k_cpu_cycle_per_tick = TOS_CFG_CPU_CLOCK / k_cpu_tick_per_second;
cpu_systick_init(k_cpu_cycle_per_tick);
#if (TOS_CFG_CPU_HRTIMER_EN > 0)
tos_cpu_hrtimer_init();
#endif
}
__KERNEL__ void cpu_systick_init(k_cycle_t cycle_per_tick)
{
port_systick_priority_set(TOS_CFG_CPU_SYSTICK_PRIO);
port_systick_config(cycle_per_tick);
}
SysTick interrupt
SysTick
Interrupt service function is the need to write our own, to be called inside look at TencentOS tiny
the function-related, update the system to run the base, the drive system of the SysTick_Handler
transplant function is as follows:
void SysTick_Handler(void)
{
HAL_IncTick();
if (tos_knl_is_running())
{
tos_knl_irq_enter();
tos_tick_handler();
tos_knl_irq_leave();
}
}
Mainly the need to call tos_tick_handler
a function of the base system updates, specific see:
__API__ void tos_tick_handler(void)
{
if (unlikely(!tos_knl_is_running())) {
return;
}
tick_update((k_tick_t)1u);
#if TOS_CFG_TIMER_EN > 0u && TOS_CFG_TIMER_AS_PROC > 0u
timer_update();
#endif
#if TOS_CFG_ROUND_ROBIN_EN > 0u
robin_sched(k_curr_task->prio);
#endif
}
Have to say that TencentOS tiny
the source code implementation is very simple 我非常喜欢
, in tos_tick_handler
, first determine what the system has been running, if not running will return directly If you have run, then call tick_update
a function to update the base system, if enabled TOS_CFG_TIMER_EN
macros representation use software timer, you need to update the appropriate treatment, for the time being not to mention here. If you enable the TOS_CFG_ROUND_ROBIN_EN
macros, also need to update the relevant time slice variable, explain later.
__KERNEL__ void tick_update(k_tick_t tick)
{
TOS_CPU_CPSR_ALLOC();
k_task_t *first, *task;
k_list_t *curr, *next;
TOS_CPU_INT_DISABLE();
k_tick_count += tick;
if (tos_list_empty(&k_tick_list)) {
TOS_CPU_INT_ENABLE();
return;
}
first = TOS_LIST_FIRST_ENTRY(&k_tick_list, k_task_t, tick_list);
if (first->tick_expires <= tick) {
first->tick_expires = (k_tick_t)0u;
} else {
first->tick_expires -= tick;
TOS_CPU_INT_ENABLE();
return;
}
TOS_LIST_FOR_EACH_SAFE(curr, next, &k_tick_list) {
task = TOS_LIST_ENTRY(curr, k_task_t, tick_list);
if (task->tick_expires > (k_tick_t)0u) {
break;
}
// we are pending on something, but tick's up, no longer waitting
pend_task_wakeup(task, PEND_STATE_TIMEOUT);
}
TOS_CPU_INT_ENABLE();
}
tick_update
The main function is to function k_tick_count +1
, and judge for themselves when the base list k_tick_list
whether the task (it can also be a delay list) timeout, if a timeout then wake up the task, otherwise direct exit. Time schedule on the sheet is very simple, the remaining time slice task variables timeslice
minus one, and then reduced to zero when the variable, the variable weight loading timeslice_reload
, and switching tasks knl_sched()
, which process is as follows:
__KERNEL__ void robin_sched(k_prio_t prio)
{
TOS_CPU_CPSR_ALLOC();
k_task_t *task;
if (k_robin_state != TOS_ROBIN_STATE_ENABLED) {
return;
}
TOS_CPU_INT_DISABLE();
task = readyqueue_first_task_get(prio);
if (!task || knl_is_idle(task)) {
TOS_CPU_INT_ENABLE();
return;
}
if (readyqueue_is_prio_onlyone(prio)) {
TOS_CPU_INT_ENABLE();
return;
}
if (knl_is_sched_locked()) {
TOS_CPU_INT_ENABLE();
return;
}
if (task->timeslice > (k_timeslice_t)0u) {
--task->timeslice;
}
if (task->timeslice > (k_timeslice_t)0u) {
TOS_CPU_INT_ENABLE();
return;
}
readyqueue_move_head_to_tail(k_curr_task->prio);
task = readyqueue_first_task_get(prio);
if (task->timeslice_reload == (k_timeslice_t)0u) {
task->timeslice = k_robin_default_timeslice;
} else {
task->timeslice = task->timeslice_reload;
}
TOS_CPU_INT_ENABLE();
knl_sched();
}
I like to focus on it!
The relevant code number can be acquired in public background.
For more information please concern "Things IoT development," the public number!