如何实现一个最简单的操作系统

Posts - 30,
Articles - 0,
Comments - 0
21:42 by Ansersion, ... 阅读,
该篇为&啰里啰嗦版&,另有相应的供参考
&不到长城非好汉;不做OS,枉为程序员&
OS之于程序员,如同梵蒂冈之于天主教徒,那永远都是块神圣的领土。若今生不能亲历之,实乃憾事!
但是,圣域不是想进就能进的呀&&
OS融合了大量的计算机的基础知识,各个知识领域之间交织紧密,初来乍到者一不小心就会绕出个死结。
我的方法是:死结就死结,不管三七二十一,直接剪断,先走下去再说,回头我们再把这个剪断的死结再接上。
我们知道,&多任务&一般在介绍OS的书籍中,是属于中间或者靠后的部分,又或者分散在各个章节中。而我决定上手就说它。
一、整体纵览:
STM32F103RC
3、文件架构:
(1)标准文件:
startup_stm32f10x_hd.s:STM32官方启动文件(注意:这是针对stm32硬件配置的文件,型号要是不同,你的可能和我不一样哦)
(2)自编文件&&这才是我们的重头戏哦(共5个文件:1x".asm"+2x".c"+2x".h"):
main.c:主函数和任务定义;
os_cpu_a.asm:中断与任务切换;
myos.c:硬件初始化与任务切换时的堆栈保存;
include.h和myos.h:两个头文件。
二、逐文解析:
顺着main函数这条主线,我们看到最终的OS其实就是执行了7行代码,共5个函数:
1、OSInit();
2、OSTaskCreate(Task1, (void*)0, (OS_STK*)&Task1Stk[TASK_STACK_SIZE-1]);
3、OSTaskCreate(Task2, (void*)0, (OS_STK*)&Task2Stk[TASK_STACK_SIZE-1]);
4、OSTaskCreate(Task3, (void*)0, (OS_STK*)&Task3Stk[TASK_STACK_SIZE-1]);
5、SysTickInit(5);
6、LedInit();
7、OSStart();简单说说main函数功能:当OSStart()执行之后,Task1、Task2、Task3轮流执行,即Task1()、Task2()、Task3()三个函数轮流执行:Task1()-&Task2()-&Task3()-&Task1()-&Task2()-&Task3()-&Task1()-&......每个任务(或函数)执行相等的时间片,并有SysTick中断来触发PendSV中断,从而实现任务切换。OSStart()执行之后,永不返回。各个函数基本做了些什么,代码后面都附加了注解。其中需要注意的地方是:OSStart()。这个函数一旦执行了就不会返还,有点像死循环(但不是死循环哦,后来会明白的)。仔细想想后,确实也应当如此,如果main函数return掉了的话,程序也就结束啦!&main函数结束&就意味着CPU现在只会喝喝茶、看看报了,什么抢劫、着火它都装作没看见。main函数就这么短,那么上面七个函数的实现在哪里呢?这时你肯定想到"#include"了吧!&太好了!#include只有一行!&那我们去include.h那里看看吧。
1 #include "include.h"
2 extern OS_TCB OSTCBTbl[OS_MAX_TASKS]; // (OS Task Control Block Table)
3 extern OS_STK TASK_IDLE_STK[TASK_STACK_SIZE]; //("TaskIdle" Stack)
4 extern OS_TCB *OSTCBC // Pointer to the current running task(OS Task Control Block Current)
5 extern OS_TCB *OSTCBN // Pointer to the next running task(OS Task Control Block Next)
6 extern INT8U OSTaskN // Index of the next task
7 extern INT32U TaskTickL // Refer to the time ticks left for the current task
8 extern INT32U TimeMS;
// For system time record
9 extern INT32U TaskTimeS // For system time record
11 OS_STK Task1Stk[TASK_STACK_SIZE]; // initialize stack for task1
12 OS_STK Task2Stk[TASK_STACK_SIZE]; // initialize stack for task2
13 OS_STK Task3Stk[TASK_STACK_SIZE]; // initialize stack for task3
15 void Task1(void *p_arg); // flip the led1 every 0.5s
16 void Task2(void *p_arg); // flip the led2 every 1.0s
17 void Task3(void *p_arg); // do nothing
19 int main(void)
// OS initialization
OSTaskCreate(Task1, (void*)0, (OS_STK*)&Task1Stk[TASK_STACK_SIZE-1]); // create task 1
OSTaskCreate(Task2, (void*)0, (OS_STK*)&Task2Stk[TASK_STACK_SIZE-1]); // create task 2
OSTaskCreate(Task3, (void*)0, (OS_STK*)&Task3Stk[TASK_STACK_SIZE-1]); // create task 3
SysTickInit(5); // configure the SysTick as 5ms
LedInit(); // leds initialization
OSStart(); // start os!
return 0; // never come here
34 void Task1(void *p_arg)
while(1) {
delayMs(100); // delay 100 * 5ms = 0.5s
LED1TURN(); // flip the switch of led1
41 void Task2(void *p_arg)
while(1) {
delayMs(200); // delay 200 * 5ms = 1.0s
LED2TURN(); // flip the switch of led2
49 void Task3(void *p_arg)
while(1) {
小白兔笔记:
(1)&啥是extern变量啊?&
"快去复习复习c语言教程吧。"
(2)"OS_TCB、OS_MAX_TASKS什么的都是些啥?&
"myos.h"里都有它们的定义。
(3)&'OSTCBCur'都是些啥怪名字?"
&针对词义复杂的变量,注意看定义那行注释,后面的括号会有对变量的简短说明,如:
extern OS_TCB *OSTCBC // Pointer to the current running task(OS Task Control Block Current)
&2、include.h
&我去!这不是欺骗我感情吗?main函数倒是1个#include,怎么到了这里却又来了三个!&
等等!先别灰心嘛,容我慢慢道来。
我们知道,main里的
#include "include.h"
其实就等于:
#include &stdlib.h&
#include "myos.h"
#include "stm32f10x.h"
但是我们为什么要那么费劲再弄一个"inlcude.h"文件呢?
假设我们想给这个OS再加个内存管理的功能,于是要添加几个".c"文件,而这些文件也要包含上面那几个"#include",那么我们不是要再把那几个&#include&都写一遍吗?&
不过,有了这个include.h之后,这些".c"文件只要
#include "include.h"就可以了。
多加了1个"include.h",但却清爽了n个&xxxxx.c"文件。
当然,这只是好处之一,其他的就不多说了,毕竟我们的主题是OS嘛。
1 #ifndef INCLUDE_H
2 #define INCLUDE_H
4 #include &stdlib.h&
5 #include "myos.h"
6 #include "stm32f10x.h" //stm32官方头文件
小白兔笔记:
(1)&#ifndef INCLUDE_H之类的东西是什么意思?&
这是为了防止多重包含头文件。
既然你问了这个问题,估计你也听不懂啥是&多重包含头文件&。
这是和编译器有关的约定,简单点说,不这么写,编译器&可能&&&我说&可能&&&找你茬。
(2)&stm32f10x.h&是官方根据stm32的各种硬件配置编写的头文件,可要找准着你的你自己的硬件配置使用哦!
1 #ifndef MYOS_H
2 #define MYOS_H
3 #include "stm32f10x.h"
5 /**********CPU DEPENDENT************/
6 #define TASK_TIME_SLICE
// 5ms for every task to run every time
8 typedef unsigned char
// Unsigned
8 bit quantity
9 typedef unsigned short INT16U;
// Unsigned 16 bit quantity
10 typedef unsigned int
// Unsigned 32 bit quantity
12 typedef unsigned int
// Each stack entry is 32-bit wide(OS Stack)
14 // assembling functions
15 void OS_ENTER_CRITICAL(void);
// Enter Critical area, that is to disable interruptions
16 void OS_EXIT_CRITICAL(void);
// Exit Critical area, that is to enable interruptions
17 void OSCtxSw(void);
// Task Switching Function(OS Context Switch)
18 void OSStart(void);
20 OS_STK* OSTaskStkInit(void (*task)(void *p_arg), // task function
void *p_arg,
// (pointer of arguments)
OS_STK *p_tos);
// (pointer to the top of stack)
23 /**********CPU INDEPENDENT************/
25 #define OS_MAX_TASKS
27 #define TASK_STATE_CREATING
28 #define TASK_STATE_RUNNING
29 #define TASK_STATE_PAUSING
31 #define TASK_STACK_SIZE
33 #define LED1TURN() (GPIOA-&ODR ^= 1&&8)
// reverse the voltage of LED1 !!!HARDWARE RELATED
34 #define LED2TURN() (GPIOD-&ODR ^= 1&&2)
// reverse the voltage of LED2 !!!HARDWARE RELATED
37 typedef struct os_tcb {
*OSTCBStkP
// (OS Task Control Block Stack Pointer)
// (OS Task Control Block Status)
40 } OS_TCB;
// (OS Task Control Block)
42 void OSInit(void);
// (OS Initialization)
43 void LedInit(void);
44 45 void OS_TaskIdle(void *p_arg);
46 void OSInitTaskIdle(void);
// (OS Initialization of "TaskIdle")
47 void OSTaskCreate(void (*task)(void *p_arg),
// task function
     void *p_arg,
      
// (pointer of arguments)
OS_STK *p_tos);         // (pointer to the top of stack)
50 void OSTCBSet(OS_TCB *p_tcb, OS_STK *p_tos, INT8U task_state);
53 void SysTickInit(INT8U Nms);
// (System Tick Initialization)
54 void SysTick_Handler(void);           
// The interrupt function
56 INT32U GetTime(void);
57 void delayMs(volatile INT32U ms);         // The argument can't be too large
这是最后一个头文件了,也是一个简单易懂的文件。同时也是最后的平原,过了这个平原,我们可就要翻雪山啦!
(1)简单说说2个函数:
void OS_ENTER_CRITICAL(void);
void OS_EXIT_CRITICAL(void);
有一种东西叫&临界区&(CRITICAL),这些所谓&临界区&指的是一些变量所在的内存,可以直接理解成&就是些特殊变量&。要访问这些变量必须得关掉&中断&,访问结束后再开启&中断&,开关&中断&就是这两个函数的任务了。猜猜看哪个是开&中断&,哪个是关&中断&呢?
(2)系统时钟中断void SysTick_Handler(void)的由来:
来自官方启动文件startup_stm32f10x_hd.s。
小白兔笔记:
小白兔表示&感觉不会再爱了&&&
4、os_cpu_a.asm和myos.c:
上文说过,这两个文件关系暧昧,扯开来只讲其中一个很没味道,这里先把它们都贴出来:
os_cpu_a.asm(&;&后的注释是对应"C"语言的解释):
OS_ENTER_CRITICAL
OS_EXIT_CRITICAL
PendSV_Handler
10 NVIC_INT_CTRL
0xE000ED04
; Address of NVIC Interruptions Control Register
11 NVIC_PENDSVSET
; Enable PendSV
12 NVIC_SYSPRI14
0xE000ED22
; System priority register (priority 14).
13 NVIC_PENDSV_PRI
; PendSV priority value (lowest).
PRESERVE8 ; align 8
|.text|, CODE, READONLY
20 ;/******************OS_ENTER_CRITICAL************/
21 OS_ENTER_CRITICAL
; Enable interruptions(Change Processor States: Interrupts Disable)
25 ;/******************OS_EXIT_CRITICAL************/
26 OS_EXIT_CRITICAL
; Disable interruptions
30 ;/******************OSStart************/
31 OSStart
; disable interruptions
; OS_ENTER_CRITICAL();
; initialize PendSV
; Set the PendSV exception priority
R0, =NVIC_SYSPRI14
; R0 = NVIC_SYSPRI14;
R1, =NVIC_PENDSV_PRI
; R1 = NVIC_PENDSV_PRI;
; *R0 = R1;
; initialize PSP as 0
; PSP = R4;
; trigger PendSV
R4, =NVIC_INT_CTRL
; R4 = NVIC_INT_CTRL;
R5, =NVIC_PENDSVSET
; R5 = NVIC_PENDSVSET;
; *R4 = R5;
; enable interruptions
; OS_EXIT_CRITICAL();
53 ; should never get here
54 ; a endless loop
55 OSStartHang
OSStartHang
58 ;/******************PendSV_Handler************/
59 PendSV_Handler
; OS_ENTER_CRITICAL();
; judge if PSP is 0 which means the task is first invoked
; R0 = PSP;
R0, PendSV_Handler_NoSave
; if(R0 == 0) goto PendSV_Handler_NoS
R12, R3, R2, R1
R0, R0, #0x20
; R0 = R0 - 0x20;
; store R4
; *R0 = R4;
R0, R0, #0x4
; R0 = R0 + 0x4;
; store R5
; *R0 = R5;
R0, R0, #0x4
; R0 = R0 + 0x4;
; store R6
; *R0 = R6;
R0, R0, #0x4
; R0 = R0 + 0x4;
; store R7
; *R0 = R7;
R0, R0, #0x4
; R0 = R0 + 0x4;
; store R8
; *R0 = R8;
R0, R0, #0x4
; R0 = R0 + 0x4;
; store R9
; *R0 = R4;
R0, R0, #0x4
; R0 = R0 + 0x4;
; store R10
; *R0 = R10;
R0, R0, #0x4
; R0 = R0 + 0x4;
; store R11
; *R0 = R11;
R0, R0, #0x4
; R0 = R0 + 0x4;
R0, R0, #0x20
; R0 = R0 - 0x20;
; easy method
R0, R0, #0x20
R0, {R4-R11}
R1, =OSTCBCur
; R1 = OSTCBC
; R1 = *R1;(R1 = OSTCBCur-&OSTCBStkPtr)
; *R1 = R0;(*(OSTCBCur-&OSTCBStkPrt) = R0)
103 PendSV_Handler_NoSave
R0, =OSTCBCur
; R0 = OSTCBC
R1, =OSTCBNext
; R1 = OSTCBN
; R2 = OSTCBNext-&OSTCBStkP
; *R0 = R2;(OSTCBCur-&OSTCBStkPtr = OSTCBNext-&OSTCBStkPtr)
; R0 = *R2;(R0 = OSTCBNext-&OSTCBStkPtr)
R0, {R4-R11}
; R4 = *R0;(R4 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; R5 = *R0;(R5 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; R6 = *R0;(R6 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; R7 = *R0;(R7 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; R8 = *R0;(R8 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; R9 = *R0;(R9 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; load R10
R10 , [R0]
; R10 = *R0;(R10 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; load R11
R11 , [R0]
; R11 = *R0;(R11 = *(OSTCBNext-&OSTCBStkPtr))
R0, R0, #0x4
; R0 = R0 + 0x4;(OSTCBNext-&OSTCBStkPtr++)
; PSP = R0;(PSP = OSTCBNext-&OSTCBStkPtr)
; P139 (key word: EXC_RETURN)
LR, LR, #0x04
; LR = LR | 0x04;
; OS_EXIT_CRITICAL();
144 OSCtxSw ;OS context switch
R4, =NVIC_INT_CTRL
; R4 = NVIC_INT_CTRL
R5, =NVIC_PENDSVSET
; R5 = NVIC_PENDSVSET
; *R4 = R5
1 #include "myos.h"
2 #include "stm32f10x.h"
5 OS_TCB OSTCBTbl[OS_MAX_TASKS]; // (OS Task Control Block Table)
6 OS_STK TASK_IDLE_STK[TASK_STACK_SIZE]; //("TaskIdle" Stack)
7 OS_TCB *OSTCBC // Pointer to the current running task(OS Task Control Block Current)
8 OS_TCB *OSTCBN // Pointer to the next running task(OS Task Control Block Next)
9 INT8U OSTaskN // Index of the next task
10 INT32U TaskTickL // Refer to the time ticks left for the current task
11 INT32U TimeMS;
12 INT32U TaskTimeS
13 char * Systick_priority = (char *)0xe000ed23;
14 // Initialize the stack of a task, it is of much relationship with the specific CPU
15 OS_STK* OSTaskStkInit(void (*task)(void *p_arg),
void *p_arg,
OS_STK *p_tos)
= (INT32U)0xL;
= (INT32U)
// Entry Point
// Don't be serious with the value below. They are of random
= (INT32U)0xFFFFFFFEL;
// R14 (LR)
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
// pointer of the argument
= (INT32U)p_
// Don't be serious with the value below. They are of random
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
47 // Only to initialize the Task Control Block Table
48 void OSInit(void)
OS_ENTER_CRITICAL();
for(i = 0; i & OS_MAX_TASKS; i++) {
OSTCBTbl[i].OSTCBStkPtr = (OS_STK*)0;
OSTCBTbl[i].OSTCBStat = TASK_STATE_CREATING;
OSInitTaskIdle();
OSTCBCur = &OSTCBTbl[0];
OSTCBNext = &OSTCBTbl[0];
OS_EXIT_CRITICAL();
62 void OSInitTaskIdle(void)
OS_ENTER_CRITICAL();
OSTCBTbl[0].OSTCBStkPtr = OSTaskStkInit(OS_TaskIdle, (void *)0, (OS_STK*)&TASK_IDLE_STK[TASK_STACK_SIZE - 1]);
OSTCBTbl[0].OSTCBStat = TASK_STATE_RUNNING;
OS_EXIT_CRITICAL();
70 void OSTaskCreate(void (*task)(void *p_arg),
void *p_arg,
OS_STK *p_tos)
INT8U i = 1;
OS_ENTER_CRITICAL();
while(OSTCBTbl[i].OSTCBStkPtr != (OS_STK*)0) {
tmp = OSTaskStkInit(task, p_arg, p_tos);
OSTCBSet(&OSTCBTbl[i], tmp, TASK_STATE_CREATING);
OS_EXIT_CRITICAL();
85 void OSTCBSet(OS_TCB *p_tcb, OS_STK *p_tos, INT8U task_state)
p_tcb-&OSTCBStkPtr = p_
p_tcb-&OSTCBStat = task_
91 void OS_TaskIdle(void *p_arg)
p_arg = p_ // No use of p_arg, only for avoiding "warning" here.
// OS_ENTER_CRITICAL();
// Nothing to do
// OS_EXIT_CRITICAL();
101 // void SysTick_Handler(void)
// OS_ENTER_CRITICAL();
// OS_EXIT_CRITICAL();
106 void SysTick_Handler(void)
OS_ENTER_CRITICAL();
if((--TaskTimeSlice) == 0){
TaskTimeSlice = TASK_TIME_SLICE;
OSTCBCur = OSTCBN
OSCtxSw();
OSTaskNext++;
while(OSTCBTbl[OSTaskNext].OSTCBStkPtr == (OS_STK*)0) {
OSTaskNext++;
if(OSTaskNext &= OS_MAX_TASKS) {
OSTaskNext = 0;
OSTCBNext = &OSTCBTbl[OSTaskNext];
TaskTimeSlice = TASK_TIME_SLICE;
OS_EXIT_CRITICAL();
127 void SysTickInit(INT8U Nms)
OS_ENTER_CRITICAL();
TimeMS = 0;
TaskTimeSlice = TASK_TIME_SLICE;
SysTick-&LOAD
= 1000 * Nms - 1;
*Systick_priority = 0x00;
SysTick-&VAL
SysTick-&CTRL = 0x3;
OS_EXIT_CRITICAL();
142 INT32U GetTime(void)
return TimeMS;
147 void delayMs(volatile INT32U ms)
tmp = GetTime() +
if(tmp & GetTime()) break;
156 void LedInit(void)
RCC-&APB2ENR |= 1&&2;
RCC-&APB2ENR |= 1&&5;
//GPIOE-&CRH&=0X0000FFFF;
//GPIOE-&CRH|=0X;
GPIOA-&CRH &= 0xfffffff0;
GPIOA-&CRH |= 0x;
//GPIOA-&ODR &= 0
GPIOA-&ODR |= 1&&8;
GPIOD-&CRL &= 0xfffff0ff;
GPIOD-&CRL |= 0x;
//GPIOD-&ODR &= 0
GPIOD-&ODR |= 1&&2;
//LED1TURN();
LED2TURN();
现在我们将按照下列过程展开叙述:
初始化OS--》创建任务--》初始化OS时间单位--》初始化LED灯--》启动OS;
&咦?怎么感觉这些步骤似曾相识呢?&
当然&相识&啦,这个就是main函数的那几行代码的意义啊!快看快看,第一行是OSInit(),这个函数到底做了些什么呢?
(1)初始化OS:
OSInit将完成以下工作:
I. &初始化全局变量OSTCBTbl结构体数组;
II. 创建一个&Idle task&;
III.初始化OSTCBCur和OSTCBNext。
1 void OSInit(void)
OS_ENTER_CRITICAL();
for(i = 0; i & OS_MAX_TASKS; i++) {
OSTCBTbl[i].OSTCBStkPtr = (OS_STK*)0;
OSTCBTbl[i].OSTCBStat = TASK_STATE_CREATING;
OSInitTaskIdle();
OSTCBCur = &OSTCBTbl[0];
OSTCBNext = &OSTCBTbl[0];
OS_EXIT_CRITICAL();
首先是第4行,就是进入&临界区&啦,也就是关中断。
接着是第5~8行的循环,其实就是初始化全局变量OSTCBTbl这个结构体数组,关键是这个结构体的指针OSTCBStkPtr,它之后会指向每个任务对应的堆栈,
此处全部初始化为&0&。当任务被创建时,它就会指向实际的内存地址,作为任务的堆栈:
第9行,创建一个&Idle Task&。此处不继续深挖它,我们后面会深讲创建一个任务的具体过程,之后就自然能看懂该函数的具体内容了。
此处要有一个概念,也就是我们在这里已经创建了一个&Task&了,即便我们后来一个&task&也不创建,CPU也会执行&Idle task&的。
然后是第10~11行,使OSTCBCur和OSTCBNext都指向&Idle task&,OSTCBCur指&OS Task Control Block Current&,
OSTCBNext当然是指&OS Task Control Block Next&咯,这两个指针是后来用于进行&任务切换&的,不知你可否体会呢^_^?
最后是第12行,离开临界区,也就是开中断。
(2)创建任务:
OSTaskCreate会完成以下工作:
I. & 找到一个&空闲的&OSTCBT
II. &初始化参数&p_tos&所指向的内存,并将其作为任务堆栈,最重要的是,使堆栈记录参数task所指向的函数的入口地址;
III. 设置新的OSTCBTbl的状态为TASK_STATE_CREATING
(这个状态变量在本OS中算是个bug,但好在没有用到这个变量,所以就暂且没管,所以你也可以暂且不管)
1 void OSTaskCreate(void (*task)(void *p_arg),
2 void *p_arg,
3 OS_STK *p_tos)
5 OS_STK *
6 INT8U i = 1;
7 OS_ENTER_CRITICAL();
8 while(OSTCBTbl[i].OSTCBStkPtr != (OS_STK*)0) {
11 tmp = OSTaskStkInit(task, p_arg, p_tos);
12 OSTCBSet(&OSTCBTbl[i], tmp, TASK_STATE_CREATING);
13 OS_EXIT_CRITICAL();
不得不提醒&小白兔们&,现在我们已经来到了这座OS之山最险峻的地方了!!!
如果实在坚持不下去了,就先休息休息。有时候,就差那么点&心领神会&,施主若是与OS有缘,那么缘分总会来的。
首先讲第8~9行的循环是什么意思:
在&OS初始化&中,我们把OSTCBTbl这个结构体数组的OSTCBStkPtr指针都初始化成了&0&,当然这些被初始化的&OSTCBTbl&都是&空闲的&,也就是没有被分配给具体任务,所以它指向堆栈地址的指针肯定是&0&,如果不是空闲的,它就应当指向具体的堆栈地址。此处循环的跳出条件就是找到某个OSTCBTbl的堆栈指针为&0&,也就是找到空闲的OSTCBTbl,此时的&i&为其偏移量。记住,&我们要创建一个新task,所以我们就需要一个空闲的OSTCBTbl来记录这个task的堆栈信息。&这样就能明白这个循环的目的了。
接着是第11行,也就是最难的地方了。
&OSTaskStkInit&,就是这个函数,它会完成以下工作:
I. & 因为参数p_tos指向堆栈栈顶(Pointer Top Of Stack),所以我们要将其依次递减,并初始化其下的一段内存中的内容。
OSTaskCreate(Task1, (void*)0, (OS_STK*)&Task1Stk[TASK_STACK_SIZE-1]);
这是main函数调用的部分,这个参数指向的正是栈顶!
1 OS_STK* OSTaskStkInit(void (*task)(void *p_arg),
void *p_arg,
OS_STK *p_tos)
= (INT32U)0xL;
= (INT32U)
// Entry Point
// Don't be serious with the value below. They are of random
= (INT32U)0xFFFFFFFEL;
// R14 (LR)
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
// pointer of the argument
= (INT32U)p_
// Don't be serious with the value below. They are of random
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
= (INT32U)0xL;
&关键是为何要这样初始化呢?第一次看的话,先从下文找点感觉,看完全部后,还需回来体味体味。
首先,参照《Cortex-M3权威指南(中文版)》P135,表9.1
中断发生时,CPU会将以上寄存器按上述顺序压入PSP中,当我们只有一个main任务需要执行时,PSP就足够帮我们保留现场的了,以至于在中断返回时,从PSP去取出先前的main任务的寄存器内容就可以了(你是不是能看出上表与我们的函数内容的对应关系呢?)。
但是&多任务&当然就不止一个main任务了,假设我们有3个任务,task1,task2,task3:
task1--》task2;PSP会保留task1的寄存器信息;
task2--》task3;PSP会再保留task2的寄存器信息,但是task1的寄存器信息就被覆盖掉了!
task3-----????-----task1
那么接下来就悲剧了。
当然,这个函数只是先给以后会用到的堆栈初始化,至于具体值并不重要,除了第8、9、19行:
第8行指定的是一个程序正常运行时,状态寄存器PSR该有的值;
第9行指定的是任务的入口地址。这个当然重要啦!因为我们的任务就是这个地址所指向的函数。
第19行指定的是任务函数参数的所在地址,因为我们一直赋值为&0&,所以暂且没有太多意义。
至于其他初始化的值,比如
(INT32U)0xL;这都无关紧要,随你怎么设置都行。
&最后第30行,函数返回堆栈指针,该指针现在指向地址的内容为0xL,依照注释,就是以后存储R4寄存器内容的地址。
&回到 OSTaskCreate函数第12行,它使得先前找到的OSTCBTbl不再&空闲了&。
(3)初始化SysTick中断和LED:
在main函数中
SysTickInit(5); LedInit();
都是与硬件相关的,唯一需要说明的是&SysTickInit(5)&当中的&5&没有太多含义,只是越大,中断发生的时间间隔就越大,具体依照硬件时钟而定。
(4)main函数最后一行:
该函数一旦执行,将永不返回。注意,接下来我们要和汇编交手了,从汇编部分(os_cpu_a.asm)第31行开始。
这里要请大家注意,每行汇编语言后都有对应的C语言注释,对照着看更容易理解。
参照《Cortex-M3权威指南(中文版)》P42,该命令即为关中断,相当于给PRIMASK写&1&。
(注:CPSID指的是属于CPS(Control Processor State)指令的Interrupt Disable指令)
第36~38行:
LDR R0, =NVIC_SYSPRI14
R1, =NVIC_PENDSV_PRI
设置PendSV中断优先级。
第42~43行:
LDR R4, &=0x0
MSR PSP, R4
初始PSP寄存器,使之为0。这个&0&表示的是我们的OS才启动,还没有任务被运行,后面很快就有说明。
第46~48行:
LDR R4, =NVIC_INT_CTRL
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
触发PendSV中断。
&什么!触发中断了!哎呀哎呀,怎么办?中断函数在哪儿?&
小白兔请先别着急,由于先前我们已经关掉中断了,所以程序还会继续往下执行,直到我们再开启中断。
由于我们真正的目的就是要开启PendSV中断,所以下一步我们要开中断啦!
开中断。由于PendSV被触发了,所以接下来CPU跳到PendSV的中断处理函数处执行。也就是os_cpu_a.asm的第59行。
第60行是关中断。
第62~63行:
R0, PendSV_Handler_NoSave
比较PSP是否为0,若是0就跳转到PendSV_Handler_NoSave处执行,由于OSStart先前将PSP初始化为0,所以就直接调到PendSV_Handler_NoSave处执行咯。
第104~107行
R0, =OSTCBCur
R1, =OSTCBNext
将OSTCBNext所指向地址的前4个字节,赋给OSTCBCur所指向地址的前4个字节,而这4个字节正是两个指针所指向结构体的OSTCBStkPtr变量!
这段代码做的就是将下个任务的堆栈指针(OSTCBNext)赋给当前任务的堆栈指针(OSTCBCur),因为PendSV中断所做的事情就是进行任务切换。
我们知道,一开始OSTCBCur和OSTCBNext一开始都是指向&Idle Task&的,所以下一个要运行的任务就是&Idle Task&。
将所要切换的堆栈指针地址赋给R0。
第112~134行:
R0, R0, #0x4
R0, R0, #0x4&
R11 , [R0]
R0, R0, #0x4
将堆栈指针R0所指向的堆栈内容赋给R4~R11。问个问题,&这些值都是多少你知道吗?&
&好了,这时候你是不是想问个问题,R0~R3怎么不给它们也赋值呢?&
回到先前那个要大家需要体味的地方
参照《Cortex-M3权威MSR
PSP, R0指南(中文版)》P135,表9.1
还有接下来第136行:
我只说一句:CPU会自动从PSP保存和加载8个量至R0~R3,R12,LR,程序入口(这个是理解的关键)和xPSR,我们无需动手。
LR, LR, #0x04
参照《Cortex-M3权威MSR&PSP, R0指南(中文版)》P139,这行是为了在回到任务后,确保继续使用PSP堆栈。
第141行开中断,此时一般还不会有中断介入,但我们要记住,现在我们还处在PendSV中断中,第142从PendSV中断返回,返回后CPU则去新的任务处执行了。
让我们再回到OS_cpu_a.asm的第62~63行,当PSP不为零,也就是已经有任务运行了,那么我们就需要先保存这个将被切换出去的任务的寄存器信息。
现在PSP指向的堆栈地址如图所示:
完成第62~66行命令之后,我们得到:
该图中有两个&R0",并不是很得体,我还是解释一下,左边的R0指的是PendSV中断下正在使用的R0寄存器,右边的R0指的是被中断的任务的R0寄存机所存储的值。
保护现场,保护现场啦!将R4~R11全部存储起来。
&为什么R0~R3不用保护呢?&
因为&&你看啊,其实CPU自己已经在PendSV中断发生时,&擅自&把它们存储了,就在上图啊!
完成第69~91行命令之后,再将R0减去0x20,堆栈就变成这样啦:
然后是第99~101行:
把R0的值赋给OSTCBCur的OSTCBStkPtr指针,这下我们就放心了,所有有关任务的信息都被存放在了相应的OSTCBTbl中了。
接下来就是该切换进入新任务了。
(5)谁来触发PendSV,实现切换任务
如果到此为止,那么CPU只会一直没完没了的执行第一个任务:Idle Task。OSStart确实完成了一次任务切换,但也仅仅就&一次&。
main函数已经没得指望了,它早就撒手不管了,那么谁来再次触发PendSV,从而执行Task1、Task2呢?
别忘了,我们还有一个关键角色没有登场呢:SysTick中断。
让我们回到myos.c的106行的SysTick的中断服务函数:SysTick_Handler。
这个函数逻辑很简单:每发生一次SysTick中断,就将TimeTaskSlice递减,当TimeTaskSlice为0时,就进行切换任务的工作,并将TimeTaskSlice的值还原。
每一次中断发生,TimeMS都会加1,从而记录整个系统的时间。
OSTCBCur = OSTCBN
将当前任务指针指向下一个任务。
OSCtxSw();
这是个汇编实现的函数,在os_cpu_a.asm的第144~150行。
非常简单,它就是在触发PendSV中断!!!
当然,这个触发不会立即发生,因为现在还处于关中断状态。
OSTaskNext++;
之前忘记介绍了,这是个全局整型变量,用来记录下一个任务的偏移量的。由于我们的任务没有优先级,只是轮换执行,所以将OSTaskNext向后偏移一个就行了。
但是,如果我们偏移到了最后一个任务怎么办呢?我们得从第一个任务重新开始才是,所以就有了第144~149的循环部分。
OSTCBNext = &OSTCBTbl[OSTaskNext];
设置新的下一个要运行的任务的任务指针。
第121行有点多余,不要也行。
第124行开中断,这行命令执行完之后,PendSV就会被触发,接着就是去执行新的任务咯。
至此,关于本OS的关键代码部分就解析完毕了。
真心希望大家多提意见,鄙人将感激涕零!}

我要回帖

更多关于 怎么重新系统 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信