Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

The concept of TencentOS tiny Scheduler and the method of starting it

2025-03-26 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces "the concept of TencentOS tiny scheduler and the method of starting scheduler". In daily operation, I believe many people have doubts about the concept of TencentOS tiny scheduler and the method of starting scheduler. Xiaobian consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful to answer the doubts about "the concept of TencentOS tiny scheduler and the method of starting scheduler". Next, please follow the editor to study!

The basic concept of scheduler

The task scheduler provided in TencentOS tiny is a full preemptive scheduling based on priority. In the process of system operation, when a task with higher priority than the current task is ready, the current task will be cut out immediately, and the high priority task will preempt the processor to run.

Creating tasks with the same priority is also allowed in the TencentOS tiny kernel. Tasks with the same priority are scheduled by time slice rotation (commonly known as time-sharing scheduler), and time-slice round robin scheduling is effective only when there are no higher priority ready tasks in the current system.

In order to ensure the real-time performance of the system, the system tries its best to ensure that the high-priority tasks can run. The principle of task scheduling is that once the state of the task changes and the priority of the currently running task is less than the highest priority of the task in the priority queue, the task is switched immediately (unless the current system is in an interrupt handler or forbids task switching).

The scheduler is the core of the operating system, and its main function is to switch tasks, that is, to find the task with the highest priority from the ready list, and then to execute it.

Start the scheduler

The scheduler is started by the cpu_sched_start function, which will be called by the tos_knl_start function, which mainly does two things: first, get the highest priority ready task in the current system through the readyqueue_highest_ready_task_get function, and assign it to the pointer k_curr_task to the current task control block, and then set the system state to the running state KNL_STATE_RUNNING.

Of course, the most important thing is to call the function cpu_sched_start written in the assembly code to start the scheduler. Under the port_s.S assembly file in the arch\ arm\ arm-v7m directory of the source code, TencentOS tiny supports chips with multiple cores, such as M3/M4/M7. Different chips implement this function differently. Port_s.S is also a bridge between TencentOS tiny as a software and CPU hardware. Take the cpu_sched_start of M4 as an example:

_ _ API__ k_err_t tos_knl_start (void) {if (tos_knl_is_running ()) {return Knowerr é KNLING;} k_next_task = readyqueue_highest_ready_task_get (); k_curr_task = karmnexttask; 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 = 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 spills, {R4-R11} IF {FPU}! = "SoftVFP"; ignore EXC_RETURN the first switch LDMFD spills, {R0} ENDIF; restore R0, R3 LDMFD spills, {R0-R3}; load R12 and LR LDMFD spills, {R12, LR} Load PC and discard xPSR LDMFD spinner, {R1, R2} CPSIE I BX R1Cortex-M kernel off interrupt instruction

From the assembly code above, I would like to introduce the Cortex-M kernel off interrupt instruction, alas, it still feels a little troublesome! In order to switch interrupts quickly, the Cortex-M kernel sets up a CPS instruction for operating PRIMASK registers and FAULTMASK registers, which are related to shielding interrupts. In addition, there are also BASEPRI registers in the Cortex-M kernel, which are also related to interrupts.

CPSID I; PRIMASK=1; off interrupt CPSIE I; PRIMASK=0; on interrupt CPSID F; FAULTMASK=1; off exception CPSIE F; FAULTMASK=0 Turn on exception register function PRIMASK after it is set to 1, it turns off all masked exceptions, leaving only NMI and HardFault FAULT to respond to FAULTMASK when it is set to 1, only NMI can respond, and all other exceptions cannot respond (including HardFault FAULT) BASEPRI this register has up to 9 bits (determined by the number of digits that express priority). It defines the threshold of the blocked priority. When it is set to a certain value, all interrupts with priority numbers greater than or equal to this value are turned off (the higher the priority number, the lower the priority). However, if set to 0, no interrupts are turned off

For more specific description, see my previous article: RTOS critical section knowledge: https://blog.csdn.net/jiejiemcu/article/details/82534974

Get back to the point

In the process of starting the kernel scheduler, you need to configure PendSV to have the lowest interrupt priority, which is to write NVIC_PENDSV_PRI (0xFF) to the NVIC_SYSPRI14 (0xE000ED22) address. Because PendSV involves system scheduling, the priority of system scheduling is lower than that of other hardware interrupts in the system, that is, priority response to external hardware interrupts in the system, so the interrupt priority of PendSV should be configured to the lowest, otherwise it is likely to generate task scheduling in the interrupt context.

The PendSV exception automatically delays the request for context change until all other ISR has finished processing. To implement this mechanism, PendSV needs to be programmed as the lowest priority exception. If OS detects that an ISR is active, it suspends a PendSV exception to suspend context switching. That is, as long as the priority of the PendSV is set to the lowest, even if the systick interrupts the IRQ, it will not immediately switch the context, but wait until the ISR is finished before the PendSV service routine executes and performs the context switch inside. The process is shown in the figure:

Then get the address of the MSP main stack pointer. In Cortex-M, 0xE000ED08 is the address of the SCB_VTOR register, which stores the starting address of the vector table.

Load the task control block pointed to by k_next_task to R2. From the previous article, we know that the first member of the task control block is the top pointer of the stack, so R2 is equal to the top pointer of the stack at this time.

Ps: when the scheduler starts, k_next_task is the same as k_curr_task (k_curr_task = k_next_task)

Load R2 to R0, then update the top stack pointer R0 to psp, and the stack pointer used when the task is executed is psp.

There are two ps:sp pointers, psp and msp. (it can be simply understood that using psp in the task context and msp in the interrupt context is not necessarily correct, which is my personal understanding.)

With R0 as the base address, load the upward growth of 8 words in the stack into the CPU register R4~R11, and R0 will also increase itself.

Then you need to load R0-R3, R12, and LR, PC, and xPSR into the CPU register group. The PC pointer points to the thread that is about to run, while the LR register points to the exit of the task. Because this is the first time to start a task, you have to manually pop all the registers on the task stack into the hardware to enter the context of the first task. Because there is no context for the first task to run at the beginning, and you need to save the above when entering PendSV, you need to manually create the task context (load these registers into the CPU register group). For the first time, this assembly entry function Sp is the k_curr_task that points to a selected task.

Look at the initialization of the task stack

From the above understanding, and then look at the initialization of the task stack, there may be a deeper impression. You can mainly understand the following points:

Get the top pointer of the stack as the stk_ BaseSTK _ size high address, and the stack of the Cortex-M kernel grows downwards.

Bits R0, R1, R2, R3, R12, R14, R15 and xPSR are automatically loaded and saved by CPU.

The bit24 of xPSR must be set to 1, that is, 0x01000000.

Entry is the entry address of the task, namely PC

R14 (LR) is the exit address of the task, so the task is usually an endless loop rather than a return

R0: arg is the formal parameter of the task body

The sp pointer will subtract when initializing the stack

_ _ 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_ Basketball [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) 0x1212121212u; / * 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: reserved, 1 * / # if defined (TOS_CFG_CPU_ARM_FPU_EN) & & (TOS_CFG_CPU_ARM_FPU_EN = = 1U) *-- sp = (cpu_data_t) 0xFFFFFFFDL *-- sp = (cpu_data_t) 0x1111111111u; / * R11 * / *-- sp = (cpu_data_t) 0x10101010u / * R10 * / *-- sp = (cpu_data_t) 0x09090909u; / * R9 * / *-- sp = (cpu_data_t) 0x08080808u; / * R8 * / *-- sp = (cpu_data_t) 0x070707u; / * R7 * / *-- sp = (cpu_data_t) 0x060606u; / * R6 * / *-sp = (cpu_data_t) 0x05050505u / * R5 * / *-- sp = (cpu_data_t) 0x04040404u; / * R4 * / return (k_stack_t *) sp;} find the highest priority task

An operating system is still not a real-time operating system if it only has the characteristic that a high-priority task can get the processor immediately and be executed. Because the process of finding the highest priority task determines whether the scheduling time is deterministic, it can be described simply by time complexity. If the time for the system to find the highest priority task is O (N), then the time will increase with the increase of the number of tasks, which is not desirable. The time complexity of TencentOS tiny is O (1). It provides two ways to find the highest priority task, which is determined by the TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT macro definition.

The first is to use the normal method to determine whether the corresponding bit is set to 1 according to the variable of k_rdyq.prio_mask [] in the ready list.

The second method is a special method, which calculates the leading zero instruction CLZ and directly obtains the position of the highest priority in the 32-bit variable k_rdyq.prio_mask []. This method is faster than the ordinary method, but is limited by the platform (hardware leading zero instruction is required, which we can use in STM32).

The implementation process is as follows. It is recommended to take a look at the readyqueue_prio_highest_get function. Its implementation is still very exquisite.

_ 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 + = KnowledgeTBLTSLOTSIZE; + + 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 tick_expires 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 ();}

The main function of the tick_update function is to k_tick_count + 1, and to determine whether the task of the time base list k_tick_list (which can also be called a delay list) times out, and if it times out, it wakes up the task, otherwise it can simply exit. The scheduling of time slices is also very simple. Reduce the remaining time slice variable timeslice of the task by one, and then when the variable is reduced to 0, reload timeslice_reload, and then switch task knl_sched (). The implementation 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 (kicking currents 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 ();} at this point, the study of "the concept of the TencentOS tiny scheduler and how to start the scheduler" is over, hoping to solve everyone's doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report