I haven't worked for a long time. I am forwarded a friend of my friend. I also want to take this opportunity to introduce one of the operating system kernels he developed. I hope the master will advise!
19bytes! Transplantation of RTOS-R & S on MCS51 2004-12-7 first draft
R & S is a clear embedded kernel. It is struggling to pursue stability and rapid; the R & S body part is strictly constructed in accordance with ANSI C standard syntax, fortunately, the current popular processor has supported standard C compilation environments, only need Change a small amount of processor related code, it is easy to transplant R & S to a specific processor; this paper explores the general transplant idea of R & S by transplantation in MCS51.
One R & S file structure
1 / Arch, depends on the specific architectural part, including interrupt, clock service, processor stack pointer switching, is the focus of transplantation;
2 / Example, presentation example
3 / INC, kernel head file, including the default configuration file config.h;
4 / kernel, basic kernel module, does not rely on part of the specific processor partial code;
5 / IPC, task communication support, including two-value semaphore, signal quantity, mutex, mailbox, message queue;
6 / lib, user support library, basic IO, file system, network support ... (to be perfected);
Two transplantation
R & S's transplantation process is very simple. In fact, just re-implement five functions and several constant macros definitions, transplantation involves files under directory / Arch:
¨ BaseType.h
¨ INTR.H
¨ trace.h
¨ Misc.c
¨ context.asm
¨ trace.c
A total of 3 head files with 3 source files, one of which ASM files.
BaseType.h includes a description of the growth direction, storage character sequence, and basic data type of CPU bit, stack.
INTR.H, MISC.C, Context.ASM defines the macro related to the critical segment, the task context switch, and the system clock interrupt service involves the following modifications:
¨ ¨ 临 Section Control Macro Critical_Enter, critical_exit and Interrupt Control Macro Disable_irq, Enable_IRQ Definition
¨ 任 Task Stack Attive__stack_init, __ entry_init
¨ Task Stack Pointer Switch __Switch_Start, __ Switch_to
¨ System clock service __timer_irs
TRACE.H, Trace.c is user interface output support, including the implementation of __assert. Provide support for R & S's Debug version, character output lib / PINTK, etc. These two files can be omitted for systems that do not have user interface hardware support.
1 Modify BaseType.h related content
#define arch_cpu_bits 8 // CPU bit wide
#define arch_stack_grow updunds // Stack growth direction
#define arch_mm_byteorder big_endian // byte sequence mode: big head mode, low address high byte
// The following is the basic data type
#define __ARCH_U8 Char
#define __ARCH_U16 SHORT
#define __ARCH_U32 LONG
#define __ARCH_U64 Long Long
// Redefile SP pointer and basic type modification (optional content)
#define __sp char IDATA *
#define __const_ code
#define __p_ data2 transplant in INTR.H, Misc.c, Context.asm
1) The critical section macro critical_enter, critical_exit definition
Disable_irq, enable_irq defines interrupt switch control.
Macro critical_enter, critical_exit defines the system's critical section code protection, which is generally achieved by opening, off interrupt implementation. In the compiler of the inline assembled statement, it is easy to define the insert assembly instruction to open, off interrupt; in C51, it is very convenient to use the C syntax to operate 51.
Here we use simple to enter the critical paragraph critical_enter, turn off the interrupt EA = 0, and exit the critical paragraph critical_exit open interrupt EA = 1 implementation. Note that this mode should avoid nesting of the critical section. In a slightly complex application, enter the critical section To save the interrupt status into the stack, then turn off the interrupt, when you exit the critical segment, recover the interrupt state from the stack.
#define disable_irq ea = 0
#define enable_irq EA = 1
#define critical_enter EA = 0
#define critical_exit EA = 1
2) Task Stack of initialization __stack_init, __ entry_init
Initialization Task Stack, actually analog function calling process, give task to ill, I am interrupted from this place, now I should go back! We use the task's entry address entry to initialize an imaginary stack so that the task can start working from returning to the entry.
In fact, R & S does not allow the task to enter the task entry function entry run, before the task begins, I should do something, this work is handed over to __entry_init, many times, just simply open interrupts for each task in __entry_init .
Static void __entry_init (void)
{
Enable_irq; // We first opened interrupt before the task begins
} // Return Entry
Now use the __stack_init to construct a initialization task stack so that the task can eventually return to the entry entry.
Struct reg_context // Constructs an initialization task stack
{
U8 Entry_L;
U8 Entry_h;
U8 Eninit_L;
U8 Eninit_h;
U8 arg;
}
Typedef struct regs_context regs_t;
U8 * __STACK_INIT (entry_t entry, arg_t arg, sp_t stack_base)
{
REGS_T * RE;
Regs = (regs_t *) stack_base; // regs points to task stack base sites
Regs-> entry_l = (u8) entry; // task entry address low
Regs-> entry_h = (u16) entry >> 8; // task entry address high
Regs-> eninit_l = (u8) __ entry_init; // Task initialization address (low)
Regs-> eninit_h = (u16) __ entry_init >> 8; // Task initialization address (high)
Regs-> arg = (u8) arg; // task entry parameters
Return (U8 *) Regs 3; // Returns the stack pointing to the task initialization function
There is a problem here, both the standard C compiler is supported by the stack transfer function parameters, but in C51, the function call is default the register pass. Therefore, the task entry parameter arg is not really incoming to the task, solving this problem is not difficult, you have to view the compiler manual, know the parameter is transmitted through that register, then extract the arg content in __entry_init to copy to the parameter register Yes.
3) Stack switching implementation __switch_start, __ switch_to
Task Stack Pointer Switching Implementation__switch_start, __switch_to is also very simple; the task stack initialization constructor and task stack pointer switch must be strictly consistent.
__switch_start function is to point the processor stack pointer SP to the first task stack process when R & S starts multiple tasks.
Void __switch_start (sp_t next_sp)
{
Sp = next_sp; // Point SP to the first task stack address
} // This is implied here, and will return to the first user task
__switch_to work is to save the sp-to the current task stack when R & S is task, and then point the stack pointer SP to the new task stack. The implementation is as follows:
Void __switch_to (sp_t * pcurrent_sp, sp_t next_sp)
{
* pcurrent_sp = (sp_t) sp; // Save the current task stack address
Sp = next_sp; // SP points to the new task stack
} // This is implied here, and will return to new user tasks
4) System clock service void __timer_irs (void)
The system clock service provides R & S with a cycle of clock beats, delay and timeout control, and clock beats generally take 10 to 100 Hz. The delay accuracy of R & S depends on the accuracy of the clock beat. You can use the timer or external dedicated timing chip to realize system section. In case of high-conditioning accuracy, the self-cycling hardware timer can be used. The typical R & S interrupt processing flow is as follows:
Off-hour; (optional)
Task scheduling lock _SCHED_LOCK (optional)
Save the scene;
Simulate an interruption
Interrupt; (optional)
Task scheduling unlock _SCHED_LOCK - (optional)
Switch task
Restore the site;
return
_SCHED_LOCK is a one-byte unsigned integer, only if the _sched_lock value is 0, the R & S scheduler is allowed to perform task switching; if interrupt is allowed, you need to lock the R & S scheduler in the interrupt to avoid nested in the interrupt. Task schedule in the middle; if you do not interrupt nested, then _sched_lock is not necessary. Based on _sched_lock can calculate R & S allows up to 255 dense interrupts.
CSEG AT 000BH
INC #_sched_lock
LJMP IT0_IRS
CSEG AT 0100H
; * ================================================================================================= * / IT0_IRS:; Interrupt entry
Push ACC; save the scene
Push B
Push PSW
Push DPH
PUSH DPL
Push 00h
Push 01h
Push 02h
Push 03h
Push 04h
Push 05H
Push 06h
Push 07h
; ------------------------------------------------- ------; Simulation interruption
Mov A, #LOW it0_out
PUSH ACC
Mov a, #high it0_out
PUSH ACC
LCALL __DO_TICK; Interrupt Processing
MOV TH0, # T0H_COUNTER; Refreshing Timer
MOV TL0, # t0l_counter;
Reti; interrupt return
; ------------------------------------------------- --------
IT0_out:
Dec #_sched_lock
LCALL __SCHEDULE; Task Switch
POP 07H; Restore Scene
POP 06H
POP 05H
POP 04H
POP 03H
POP 02H
POP 01H
POP 00H
POP DPL
POP DPH
POP PSW
POP B
POP ACC
RET
; * ================================================================================================= * /
Interrupt processing Key ideas is that intercepted the original interrupt, saves task field, then simulates the interrupt, according to the interrupt processing mode and content, whether the task switching __schedule process is scheduled to schedule if the return point is scheduled.
Maybe someone will ask, why do you want to interrupt analog process? On some processors, the interrupts and functions returns only different in the stack content, and some other processors also contain some recovery operations for special registers; the general approach is to separate the task level and interruptive task switching process Processing, especially, R & S is logically separated by simulation, and logically separates the interrupt task to degenerate into task level, so that the switching of the entire task and the processing of the stack becomes very simple and clear, and simplifies the workload of transplantation. .
3 Modify system configuration files
Designed ideas for different bit wide processors, R & S has very good scalability, in order to meet this requirement, it is necessary to divide and abstrafigure the kernel to make a very careful function module, each block is detachable. This can be small and exquisite on the 8-bit processor, and the complex business can be processed on the 32-bit processor. All module configurations of R & S are set in the file: inc / config.h
This is the default configuration file. In practical applications, it is recommended to copy the configuration file to another directory to facilitate the use and recovery.
Here, we only retain basic scheduling functions, define the maximum number of tasks of the system, using simple priority mode; including a system must have the IDLE task, users can create two tasks, the highest priority task is 0, other The module is turned off so that we get a very small R & S with only 1K size after compiling.
#define cfg_max_tasks 3 // Defines the maximum number of tasks
#define cfg_prio_mode 0 // Using simple priority mode
#define cfg_idle_stacksz 20 // Defines the IDLE Task Stack Size
Now, R & S has been working on MCS51. In C51, the calculation of the size of the task stack is based on the task function nested layer, coupled with the worst case of the nesting of the interrupt; R & S uses the RAM resource differently depending on the specific module configuration parameters, in this example, The RAM resources that can be calculated to scheduling are 4N 3 bytes, n is the number of tasks 3, and R & S's demand for resources is very savings.
Plus the IDLE Task Stack 20bytes, we actually use 35bytes! It is very excited. Oh, I haven't paled 19bytes' origin. In now, we are all carried out in accordance with general processor transplant ideas, but for MCS51, we can also optimize ...
Three optimization - implement 19bytes!
The careful friends have found that the task stack is mainly consumed in the interrupt processing. It requires a large number of on-site protection resources, and the system task IDle is just a simple empty cycle, and there is no protected field register, which is our entry point. We started from the following:
¨ 减 Reduce the number of functional nested layers
¨ Prohibition of interrupt nesting
¨ For the iDLE task, special treatment on the interruption site
Reducing the number of nested layers and disabling interrupts are meaningful for reducing the user task stack. If the stack resource consumed by the IDLE task can be reduced, there should be a lot of RAM overhead as a whole.
We mainly modify the clock interrupt processing section
CSEG AT 000BH
CLR EA;
LJMP IT0_IRS
CSEG AT 0100H
; * ================================================================================================= * /
IT0_IRS:; Interrupt entry
Push ACC; save the scene
Mov A, #task_idle_prio
CJNE A, _CURRENT_PRIO, IT0_NOR_IN; determine if the current task is IDLE task
POP ACC; if the IDLE task, skip the field protection process JMP it0_idle_in
IT0_NOR_IN:; if it is a general task, according to normal flow
Push B
Push PSW
Push DPH
PUSH DPL
Push 00h
Push 01h
Push 02h
Push 03h
Push 04h
Push 05H
Push 06h
Push 07h
IT0_Idle_IN:
; ------------------------------------------------- ------; Simulation interruption
LCALL __MCS51_DO_TICK; Interrupt Processing
Mov A, #LOW it0_out
PUSH ACC
Mov a, #high it0_out
PUSH ACC
MOV TH0, # T0H_COUNTER; Refreshing Timer
MOV TL0, # t0l_counter;
Reti; interrupt return
; ------------------------------------------------- --------
IT0_out:
Mov a, #task_idle_prio; impening the interrupt processing process
CJNE A, _CURRENT_PRIO, IT0_NOR_OUT
LJMP __SCHEDULE
IT0_NOR_OUT:
LCALL __SCHEDULE; Task Switch
POP 07H; Restore Scene
POP 06H
POP 05H
POP 04H
POP 03H
POP 02H
POP 01H
POP 00H
POP DPL
POP DPH
POP PSW
POP B
POP ACC
RET
; * ================================================================================================= * /
Here we use the current task if the current task is an IDLE task, if it is a IDLE task, skip the on-site protection process;
And we started to join the interrupt statement in the interrupt service, which will bring a problem, and it contains the critical segment code in the interrupt process __do_tick, __ do_tick returns to open the interrupt, that is, we mentioned the incident section Problem, we can avoid this problem by using the nested critical paragraph mode, here I don't want to increase the burden of the task stack, change the original __do_tick implementation to __mcs51_do_tick, just simply remove the __do_tick's neutral paragraph.
And adjust the position of __mcs51_do_tick, which reduces a layer (2bytes) stack depth.
Careful readers will find that in the current task is used in the interrupt processes of IDLE:
LJMP __SCHEDULE instead of lcall __schedule
Yes, this can reduce a stack depth, the efficiency is higher, but these are not the main reason, and I will analyze the key to doing this. After such a toss, in the interrupt process of the IDLE task, the largest stack depth, yes, it is 4bytes! Don't forget to initialize the task stack __stack_init, use 5Bytes's stack space, R & S calls __stack_init to initialize the IDLE Task Stack when establishing your IDLE task, so you need to do some small surgery for IDLE tasks, so we can CFG_Idle_stacksz is changed to:
#define cfg_idle_stacksz 4 // Defines the IDLE Task Stack Size
Compilation! run! Success! --19bytes!
The key to the use of ljmp __schedule is that because of the IDLE task, use LCALL when __schedule returns, it will allow interrupts, this time the IDLE task has a stack, before returning the IDLE task, if it happens Interrupted, IDLE Task Stack has reached a 4Bytes critical value, any PUSH operation in the interrupt service will cause the IDLE's stack to overflow! Although this happens is minimal, this is not allowed. In particular, in the case of using the task stack, we must carefully consider every detail, and any errors may cause the system's crash.
Finally, pay attention:
If you use the extreme iDle stack, be sure to ensure that the IDLE stack is unwaffected.
To use the system IDLE hook hook_idle_task, must be very cautious, make sure not cause the IDLE stack overflow
Make sure that other interrupt treatments do not cause iDLE stack overflow
When there are excess resources in the system, the appropriate amount of the stack for each task stack is wise.
The above instance code is in Keilc 6.12, using the small RAM mode of AT80C52, I have incorporated the transplant code into the R & S informal version: V1.12b, all source code can be
Http://www.01s.org Download, in the new R & S regular version, will contain 51 and other processor transplant code, please feel free to follow the 01S website embedded special edition.
Before the article, I have already spent several nights to check the transplant code to maximize accuracy and readability. In fact, this is the first time I use Keilc. The knowledge of 51 is only stayed during the 99-year-old summer competition, and I have been re-picked up for many years. If you have any places or bugs, or any problem, pass
Ruanhaishen@01s.org or
http://www.01s.org
Can be contacted with me, and I will be posted on the above website for the latest correction versions of this article.
Reprint this article, please keep integrity