一步步学习操作系统(1)——参照ucos,在STM32上实现一个简单的多任务

[复制链接]
7458|33
 楼主 | 2015-4-28 09:39 | 显示全部楼层 |阅读模式
“不到长城非好汉;不做OS,枉为程序员”

OS之于程序员,如同梵蒂冈之于天主教徒,那永远都是块神圣的领土。若今生不能亲历之,实乃憾事!

但是,圣域不是想进就能进的呀……

OS融合了大量的计算机的基础知识,各个知识领域之间交织紧密,初来乍到者一不小心就会绕出个死结。

我的方法是:死结就死结,不管三七二十一,直接剪断,先走下去再说,回头我们再把这个剪断的死结再接上。



我们知道,“多任务”一般在介绍OS的书籍中,是属于中间或者靠后的部分,又或者分散在各个章节中。而我决定上手就说它。
 楼主 | 2015-4-28 09:40 | 显示全部楼层
一、整体纵览:

1、硬件:

STM32F103RC

2、IDE:

MDK5

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:两个头文件。
 楼主 | 2015-4-28 09:41 | 显示全部楼层
二、逐文解析:

1、main.c

顺着main函数这条主线,我们看到最终的OS其实就是执行了7行代码,共5个函数:
  1. 1、OSInit();
  2. 2、OSTaskCreate(Task1, (void*)0, (OS_STK*)&Task1Stk[TASK_STACK_SIZE-1]);
  3. 3、OSTaskCreate(Task2, (void*)0, (OS_STK*)&Task2Stk[TASK_STACK_SIZE-1]);
  4. 4、OSTaskCreate(Task3, (void*)0, (OS_STK*)&Task3Stk[TASK_STACK_SIZE-1]);
  5. 5、SysTickInit(5);
  6. 6、LedInit();
  7. 7、OSStart();
复制代码
 楼主 | 2015-4-28 09:42 | 显示全部楼层
简单说说main函数功能:
当OSStart()执行之后,Task1、Task2、Task3轮流执行,即Task1()、Task2()、Task3()三个函数轮流执行:
Task1()->Task2()->Task3()->Task1()->Task2()->Task3()->Task1()->......
每个任务(或函数)执行相等的时间片,并有SysTick中断来触发PendSV中断,从而实现任务切换。
OSStart()执行之后,永不返回。
 楼主 | 2015-4-28 09:42 | 显示全部楼层
各个函数基本做了些什么,代码后面都附加了注解。
其中需要注意的地方是:OSStart()。
这个函数一旦执行了就不会返还,有点像死循环(但不是死循环哦,后来会明白的)。
仔细想想后,确实也应当如此,如果main函数return掉了的话,程序也就结束啦!
“main函数结束”就意味着CPU现在只会喝喝茶、看看报了,什么抢劫、着火它都装作没看见。
 楼主 | 2015-4-28 09:43 | 显示全部楼层
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 *OSTCBCur; // Pointer to the current running task(OS Task Control Block Current)
  5. extern OS_TCB *OSTCBNext; // Pointer to the next running task(OS Task Control Block Next)
  6. extern INT8U OSTaskNext; // Index of the next task
  7. extern INT32U TaskTickLeft; // Refer to the time ticks left for the current task
  8. extern INT32U TimeMS;       // For system time record                             
  9. extern INT32U TaskTimeSlice; // For system time record

  10. OS_STK Task1Stk[TASK_STACK_SIZE]; // initialize stack for task1
  11. OS_STK Task2Stk[TASK_STACK_SIZE]; // initialize stack for task2
  12. OS_STK Task3Stk[TASK_STACK_SIZE]; // initialize stack for task3

  13. void Task1(void *p_arg); // flip the led1 every 0.5s
  14. void Task2(void *p_arg); // flip the led2 every 1.0s
  15. void Task3(void *p_arg); // do nothing

  16. int main(void)
  17. {
  18.    
  19.    
  20.     OSInit();  // OS initialization
  21.     OSTaskCreate(Task1, (void*)0, (OS_STK*)&Task1Stk[TASK_STACK_SIZE-1]); // create task 1
  22.     OSTaskCreate(Task2, (void*)0, (OS_STK*)&Task2Stk[TASK_STACK_SIZE-1]); // create task 2
  23.     OSTaskCreate(Task3, (void*)0, (OS_STK*)&Task3Stk[TASK_STACK_SIZE-1]); // create task 3
  24.     SysTickInit(5); // configure the SysTick as 5ms
  25.     LedInit(); // leds initialization
  26.     OSStart(); // start os!
  27.    
  28.     return 0; // never come here
  29. }

  30. void Task1(void *p_arg)
  31. {
  32.     while(1) {
  33.         delayMs(100); // delay 100 * 5ms = 0.5s
  34.         LED1TURN(); // flip the switch of led1
  35.     }
  36. }
  37. void Task2(void *p_arg)
  38. {
  39.     while(1) {
  40.         delayMs(200); // delay 200 * 5ms = 1.0s
  41.         LED2TURN(); // flip the switch of led2
  42.     }
  43. }

  44. void Task3(void *p_arg)
  45. {
  46.     while(1) {
  47.     }
  48. }
复制代码
 楼主 | 2015-4-28 09:44 | 显示全部楼层
小白兔笔记:

(1)“啥是extern变量啊?”

"快去复习复习c语言教程吧。"

(2)"OS_TCB、OS_MAX_TASKS什么的都是些啥?“

"myos.h"里都有它们的定义。

(3)”'OSTCBCur'都是些啥怪名字?"

“针对词义复杂的变量,注意看定义那行注释,后面的括号会有对变量的简短说明,如:

extern OS_TCB *OSTCBCur; // Pointer to the current running task(OS Task Control Block Current)
 楼主 | 2015-4-28 09:45 | 显示全部楼层
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

  3. #include <stdlib.h>
  4. #include "myos.h"
  5. #include "stm32f10x.h" //stm32官方头文件

  6. #endif
复制代码
 楼主 | 2015-4-28 09:46 | 显示全部楼层
小白兔笔记:

(1)“#ifndef INCLUDE_H之类的东西是什么意思?”

这是为了防止多重包含头文件。

既然你问了这个问题,估计你也听不懂啥是”多重包含头文件“。

这是和编译器有关的约定,简单点说,不这么写,编译器”可能“——我说”可能“——找你茬。

(2)”stm32f10x.h“是官方根据stm32的各种硬件配置编写的头文件,可要找准着你的你自己的硬件配置使用哦!
 楼主 | 2015-4-28 09:55 | 显示全部楼层
3、myos.h
  1. #ifndef MYOS_H
  2. #define MYOS_H
  3. #include "stm32f10x.h"

  4. /**********CPU DEPENDENT************/
  5. #define TASK_TIME_SLICE     5             // 5ms for every task to run every time

  6. typedef unsigned char  INT8U;             // Unsigned  8 bit quantity  
  7. typedef unsigned short INT16U;            // Unsigned 16 bit quantity
  8. typedef unsigned int   INT32U;            // Unsigned 32 bit quantity

  9. typedef unsigned int   OS_STK;            // Each stack entry is 32-bit wide(OS Stack)

  10. // assembling functions
  11. void OS_ENTER_CRITICAL(void);             // Enter Critical area, that is to disable interruptions
  12. void OS_EXIT_CRITICAL(void);              // Exit Critical area, that is to enable interruptions
  13. void OSCtxSw(void);                       // Task Switching Function(OS Context Switch)
  14. void OSStart(void);

  15. OS_STK* OSTaskStkInit(void (*task)(void *p_arg), // task function
  16.               void *p_arg,                       // (pointer of arguments)
  17.                 OS_STK *p_tos);                  // (pointer to the top of stack)
  18. /**********CPU INDEPENDENT************/

  19. #define OS_MAX_TASKS    16

  20. #define TASK_STATE_CREATING     0
  21. #define TASK_STATE_RUNNING    1
  22. #define TASK_STATE_PAUSING    2

  23. #define TASK_STACK_SIZE     64
  24.               
  25. #define LED1TURN() (GPIOA->ODR ^= 1<<8)  // reverse the voltage of LED1 !!!HARDWARE RELATED
  26. #define LED2TURN() (GPIOD->ODR ^= 1<<2)  // reverse the voltage of LED2 !!!HARDWARE RELATED


  27. typedef struct os_tcb {
  28.     OS_STK    *OSTCBStkPtr;     // (OS Task Control Block Stack Pointer)
  29.     INT8U     OSTCBStat;        // (OS Task Control Block Status)
  30. } OS_TCB;                       // (OS Task Control Block)

  31. void OSInit(void);              // (OS Initialization)
  32. void LedInit(void);
  33. 45 void OS_TaskIdle(void *p_arg);
  34. void OSInitTaskIdle(void);                      // (OS Initialization of "TaskIdle")
  35. void OSTaskCreate(void (*task)(void *p_arg),    // task function
  36.                void *p_arg,              // (pointer of arguments)
  37.                   OS_STK *p_tos);         // (pointer to the top of stack)
  38. void OSTCBSet(OS_TCB *p_tcb, OS_STK *p_tos, INT8U task_state);


  39. void SysTickInit(INT8U Nms);                    // (System Tick Initialization)
  40. void SysTick_Handler(void);              // The interrupt function

  41. INT32U GetTime(void);
  42. void delayMs(volatile INT32U ms);         // The argument can't be too large

  43. #endif
复制代码
 楼主 | 2015-4-28 09:57 | 显示全部楼层
(1)简单说说2个函数:

void OS_ENTER_CRITICAL(void);

void OS_EXIT_CRITICAL(void);


有一种东西叫“临界区”(CRITICAL),这些所谓“临界区”指的是一些变量所在的内存,可以直接理解成“就是些特殊变量”。
要访问这些变量必须得关掉“中断”,访问结束后再开启“中断”,开关“中断”就是这两个函数的任务了。
猜猜看哪个是开“中断”,哪个是关“中断”呢?
 楼主 | 2015-4-28 09:57 | 显示全部楼层
(2)系统时钟中断void SysTick_Handler(void)的由来:

来自官方启动文件startup_stm32f10x_hd.s。



小白兔笔记:

小白兔表示“感觉不会再爱了……”
 楼主 | 2015-4-28 09:58 | 显示全部楼层
4、os_cpu_a.asm和myos.c:

上文说过,这两个文件关系暧昧,扯开来只讲其中一个很没味道,这里先把它们都贴出来:

os_cpu_a.asm(“;”后的注释是对应"C"语言的解释):
  1. IMPORT     OSTCBCur
  2.     IMPORT    OSTCBNext
  3.    
  4.     EXPORT    OS_ENTER_CRITICAL
  5.     EXPORT    OS_EXIT_CRITICAL
  6.     EXPORT    OSStart
  7.     EXPORT    PendSV_Handler
  8.     EXPORT    OSCtxSw
  9.    
  10. NVIC_INT_CTRL    EQU            0xE000ED04    ; Address of NVIC Interruptions Control Register
  11. NVIC_PENDSVSET   EQU            0x10000000    ; Enable PendSV
  12. NVIC_SYSPRI14    EQU         0xE000ED22  ; System priority register (priority 14).
  13. NVIC_PENDSV_PRI  EQU         0xFF        ; PendSV priority value (lowest).
  14.    
  15.     PRESERVE8 ; align 8

  16.     AREA    |.text|, CODE, READONLY
  17.     THUMB

  18. ;/******************OS_ENTER_CRITICAL************/
  19. OS_ENTER_CRITICAL
  20.     CPSID    I    ; Enable interruptions(Change Processor States: Interrupts Disable)
  21.     BX    LR    ; Return

  22. ;/******************OS_EXIT_CRITICAL************/
  23. OS_EXIT_CRITICAL
  24.     CPSIE    I    ; Disable interruptions
  25.     BX    LR     ; Return

  26. ;/******************OSStart************/
  27. OSStart
  28.     ; disable interruptions
  29.     CPSID    I                            ; OS_ENTER_CRITICAL();
  30.     ; initialize PendSV
  31.     ; Set the PendSV exception priority
  32.     LDR     R0, =NVIC_SYSPRI14            ; R0 = NVIC_SYSPRI14;
  33.     LDR     R1, =NVIC_PENDSV_PRI          ; R1 = NVIC_PENDSV_PRI;
  34.     STRB    R1, [R0]                      ; *R0 = R1;
  35.    
  36.     ; initialize PSP as 0
  37.     ; MOV    R4, #0
  38.     LDR R4,  =0x0                            ; R4 = 0;
  39.     MSR    PSP, R4                           ; PSP = R4;
  40.    
  41.     ; trigger PendSV
  42.     LDR    R4, =NVIC_INT_CTRL              ; R4 = NVIC_INT_CTRL;
  43.     LDR    R5, =NVIC_PENDSVSET             ; R5 = NVIC_PENDSVSET;
  44.     STR    R5, [R4]                        ; *R4 = R5;
  45.    
  46.     ; enable interruptions
  47.     CPSIE    I                            ; OS_EXIT_CRITICAL();

  48. ; should never get here
  49. ; a endless loop
  50. OSStartHang                                    
  51.     B    OSStartHang

  52. ;/******************PendSV_Handler************/
  53. PendSV_Handler
  54.     CPSID    I                            ; OS_ENTER_CRITICAL();
  55.     ; judge if PSP is 0 which means the task is first invoked
  56.     MRS     R0, PSP                            ; R0 = PSP;
  57.     CBZ     R0, PendSV_Handler_NoSave          ; if(R0 == 0) goto PendSV_Handler_NoSave;
  58.    
  59.     ;     R12, R3, R2, R1
  60.     SUB     R0, R0, #0x20            ; R0 = R0 - 0x20;
  61.    
  62.     ; store R4
  63.     STR     R4 , [R0]                ; *R0 = R4;
  64.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
  65.     ; store R5
  66.     STR     R5 , [R0]                ; *R0 = R5;
  67.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
  68.     ; store R6
  69.     STR     R6 , [R0]                ; *R0 = R6;
  70.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
  71.     ; store R7
  72.     STR     R7 , [R0]                ; *R0 = R7;
  73.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
  74.     ; store R8
  75.     STR     R8 , [R0]                ; *R0 = R8;
  76.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
  77.     ; store R9
  78.     STR     R9, [R0]                ; *R0 = R4;
  79.     ADD     R0, R0, #0x4            ; R0 = R0 + 0x4;
  80.     ; store R10
  81.     STR     R10, [R0]               ; *R0 = R10;
  82.     ADD     R0, R0, #0x4            ; R0 = R0 + 0x4;
  83.     ; store R11
  84.     STR     R11, [R0]               ; *R0 = R11;
  85.     ADD     R0, R0, #0x4            ; R0 = R0 + 0x4;

  86.     SUB     R0, R0, #0x20           ; R0 = R0 - 0x20;
  87.    
  88.     ; easy method
  89.     ;SUB     R0, R0, #0x20
  90.     ;STM     R0, {R4-R11}
  91.    
  92.     LDR     R1, =OSTCBCur            ; R1 = OSTCBCur;
  93.     LDR     R1, [R1]                 ; R1 = *R1;(R1 = OSTCBCur->OSTCBStkPtr)
  94.     STR     R0, [R1]                 ; *R1 = R0;(*(OSTCBCur->OSTCBStkPrt) = R0)

  95. PendSV_Handler_NoSave
  96.     LDR     R0, =OSTCBCur           ; R0 = OSTCBCur;
  97.     LDR     R1, =OSTCBNext          ; R1 = OSTCBNext;
  98.     LDR     R2, [R1]                ; R2 = OSTCBNext->OSTCBStkPtr;
  99.     STR     R2, [R0]                ; *R0 = R2;(OSTCBCur->OSTCBStkPtr = OSTCBNext->OSTCBStkPtr)
  100.    
  101.     LDR     R0, [R2]                 ; R0 = *R2;(R0 = OSTCBNext->OSTCBStkPtr)
  102.     ; LDM     R0, {R4-R11}
  103.     ; load R4
  104.     LDR     R4, [R0]                 ; R4 = *R0;(R4 = *(OSTCBNext->OSTCBStkPtr))
  105.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  106.     ; load R5
  107.     LDR     R5, [R0]                 ; R5 = *R0;(R5 = *(OSTCBNext->OSTCBStkPtr))
  108.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  109.     ; load R6
  110.     LDR     R6, [R0]                 ; R6 = *R0;(R6 = *(OSTCBNext->OSTCBStkPtr))
  111.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  112.     ; load R7
  113.     LDR     R7 , [R0]                ; R7 = *R0;(R7 = *(OSTCBNext->OSTCBStkPtr))
  114.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  115.     ; load R8
  116.     LDR     R8 , [R0]                ; R8 = *R0;(R8 = *(OSTCBNext->OSTCBStkPtr))
  117.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  118.     ; load R9
  119.     LDR     R9 , [R0]                ; R9 = *R0;(R9 = *(OSTCBNext->OSTCBStkPtr))
  120.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  121.     ; load R10
  122.     LDR     R10 , [R0]               ; R10 = *R0;(R10 = *(OSTCBNext->OSTCBStkPtr))
  123.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  124.     ; load R11
  125.     LDR     R11 , [R0]               ; R11 = *R0;(R11 = *(OSTCBNext->OSTCBStkPtr))
  126.     ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
  127.    
  128.     MSR     PSP, R0                 ; PSP = R0;(PSP = OSTCBNext->OSTCBStkPtr)
  129.     ; P42
  130.     ; P139 (key word: EXC_RETURN)
  131.     ; use PSP
  132.     ORR     LR, LR, #0x04           ; LR = LR | 0x04;
  133.     CPSIE     I                     ; OS_EXIT_CRITICAL();
  134.     BX    LR                        ; return;

  135. OSCtxSw ;OS context switch
  136.     PUSH    {R4, R5}               
  137.     LDR     R4, =NVIC_INT_CTRL       ; R4 = NVIC_INT_CTRL
  138.     LDR     R5, =NVIC_PENDSVSET      ; R5 = NVIC_PENDSVSET
  139.     STR     R5, [R4]                 ; *R4 = R5
  140.     POP     {R4, R5}
  141.     BX     LR                        ; return;
  142.    
  143.     align 4
  144.     end
复制代码
 楼主 | 2015-4-28 09:59 | 显示全部楼层
myos.c:
  1. #include "myos.h"
  2. #include "stm32f10x.h"


  3. OS_TCB OSTCBTbl[OS_MAX_TASKS]; // (OS Task Control Block Table)
  4. OS_STK TASK_IDLE_STK[TASK_STACK_SIZE]; //("TaskIdle" Stack)
  5. OS_TCB *OSTCBCur; // Pointer to the current running task(OS Task Control Block Current)
  6. OS_TCB *OSTCBNext; // Pointer to the next running task(OS Task Control Block Next)
  7. INT8U OSTaskNext; // Index of the next task
  8. INT32U TaskTickLeft; // Refer to the time ticks left for the current task
  9. INT32U TimeMS;
  10. INT32U TaskTimeSlice;
  11. char * Systick_priority = (char *)0xe000ed23;
  12. // Initialize the stack of a task, it is of much relationship with the specific CPU
  13. OS_STK* OSTaskStkInit(void (*task)(void *p_arg),
  14.           void *p_arg,
  15.           OS_STK *p_tos)
  16. {
  17.     OS_STK *stk;
  18.     stk = p_tos;

  19.     *(stk)    = (INT32U)0x01000000L;             // xPSR                                               
  20.     *(--stk)  = (INT32U)task;                    // Entry Point  

  21.     // Don't be serious with the value below. They are of random
  22.     *(--stk)  = (INT32U)0xFFFFFFFEL;             // R14 (LR)
  23.     *(--stk)  = (INT32U)0x12121212L;             // R12                                                
  24.     *(--stk)  = (INT32U)0x03030303L;             // R3                                                
  25.     *(--stk)  = (INT32U)0x02020202L;             // R2                                                
  26.     *(--stk)  = (INT32U)0x01010101L;             // R1                                                

  27.     // pointer of the argument
  28.     *(--stk)  = (INT32U)p_arg;                   // R0

  29.     // Don't be serious with the value below. They are of random
  30.     *(--stk)  = (INT32U)0x11111111L;             // R11
  31.     *(--stk)  = (INT32U)0x10101010L;             // R10
  32.     *(--stk)  = (INT32U)0x09090909L;             // R9  
  33.     *(--stk)  = (INT32U)0x08080808L;             // R8  
  34.     *(--stk)  = (INT32U)0x07070707L;             // R7  
  35.     *(--stk)  = (INT32U)0x06060606L;             // R6  
  36.     *(--stk)  = (INT32U)0x05050505L;             // R5  
  37.     *(--stk)  = (INT32U)0x04040404L;             // R4  
  38.     return stk;
  39. }

  40. // Only to initialize the Task Control Block Table
  41. void OSInit(void)
  42. {
  43.     INT8U i;
  44.     OS_ENTER_CRITICAL();
  45.     for(i = 0; i < OS_MAX_TASKS; i++) {
  46.         OSTCBTbl[i].OSTCBStkPtr = (OS_STK*)0;
  47.         OSTCBTbl[i].OSTCBStat = TASK_STATE_CREATING;
  48.     }
  49.     OSInitTaskIdle();
  50.     OSTCBCur = &OSTCBTbl[0];
  51.     OSTCBNext = &OSTCBTbl[0];
  52.     OS_EXIT_CRITICAL();
  53. }

  54. void OSInitTaskIdle(void)
  55. {
  56.     OS_ENTER_CRITICAL();
  57.     OSTCBTbl[0].OSTCBStkPtr = OSTaskStkInit(OS_TaskIdle, (void *)0, (OS_STK*)&TASK_IDLE_STK[TASK_STACK_SIZE - 1]);
  58.     OSTCBTbl[0].OSTCBStat = TASK_STATE_RUNNING;
  59.     OS_EXIT_CRITICAL();
  60. }

  61. void OSTaskCreate(void (*task)(void *p_arg),
  62.           void *p_arg,
  63.           OS_STK *p_tos)
  64. {
  65.     OS_STK * tmp;
  66.     INT8U i = 1;
  67.     OS_ENTER_CRITICAL();
  68.     while(OSTCBTbl[i].OSTCBStkPtr != (OS_STK*)0) {
  69.         i++;
  70.     }
  71.     tmp = OSTaskStkInit(task, p_arg, p_tos);
  72.     OSTCBSet(&OSTCBTbl[i], tmp, TASK_STATE_CREATING);
  73.     OS_EXIT_CRITICAL();
  74. }

  75. void OSTCBSet(OS_TCB *p_tcb, OS_STK *p_tos, INT8U task_state)
  76. {
  77.     p_tcb->OSTCBStkPtr = p_tos;
  78.     p_tcb->OSTCBStat = task_state;
  79. }

  80. void OS_TaskIdle(void *p_arg)
  81. {
  82.     p_arg = p_arg; // No use of p_arg, only for avoiding "warning" here.
  83.     for(;;) {
  84.         // OS_ENTER_CRITICAL();
  85.         // Nothing to do
  86.         // OS_EXIT_CRITICAL();
  87.     }
  88. }

  89. // void SysTick_Handler(void)
  90. // {
  91. //     // OS_ENTER_CRITICAL();
  92. //     // OS_EXIT_CRITICAL();
  93. // }
  94. void SysTick_Handler(void)
  95. {
  96.     OS_ENTER_CRITICAL();
  97.     if((--TaskTimeSlice) == 0){
  98.         TaskTimeSlice = TASK_TIME_SLICE;
  99.         OSTCBCur = OSTCBNext;
  100.         OSCtxSw();
  101.         OSTaskNext++;
  102.         while(OSTCBTbl[OSTaskNext].OSTCBStkPtr == (OS_STK*)0) {
  103.             OSTaskNext++;
  104.             if(OSTaskNext >= OS_MAX_TASKS) {
  105.                 OSTaskNext = 0;
  106.             }
  107.         }
  108.         OSTCBNext = &OSTCBTbl[OSTaskNext];
  109.         TaskTimeSlice = TASK_TIME_SLICE;
  110.     }
  111.     TimeMS++;
  112.     OS_EXIT_CRITICAL();
  113. }

  114. void SysTickInit(INT8U Nms)
  115. {
  116.    
  117.     OS_ENTER_CRITICAL();
  118.    
  119.     TimeMS = 0;
  120.     TaskTimeSlice = TASK_TIME_SLICE;                             

  121.     SysTick->LOAD  = 1000 * Nms - 1;
  122.     *Systick_priority = 0x00;
  123.     SysTick->VAL   = 0;  
  124.     SysTick->CTRL = 0x3;
  125.     OS_EXIT_CRITICAL();
  126. }

  127. INT32U GetTime(void)
  128. {
  129.     return TimeMS;
  130. }

  131. void delayMs(volatile INT32U ms)
  132. {
  133.     INT32U tmp;
  134.     tmp = GetTime() + ms;
  135.     while(1){
  136.         if(tmp < GetTime()) break;
  137.     }
  138. }

  139. void LedInit(void)
  140. {
  141.     RCC->APB2ENR |= 1<<2;
  142.     RCC->APB2ENR |= 1<<5;
  143.     //GPIOE->CRH&=0X0000FFFF;  
  144.     //GPIOE->CRH|=0X33330000;
  145.    
  146.     GPIOA->CRH &= 0xfffffff0;
  147.     GPIOA->CRH |= 0x00000003;
  148.     //GPIOA->ODR &= 0xfffffeff;
  149.     GPIOA->ODR |= 1<<8;

  150.     GPIOD->CRL &= 0xfffff0ff;
  151.     GPIOD->CRL |= 0x00000300;
  152.     //GPIOD->ODR &= 0xfffffffd;   
  153.     GPIOD->ODR |= 1<<2;
  154.    
  155.     //LED1TURN();
  156.     LED2TURN();
  157.    
  158. }
复制代码
 楼主 | 2015-4-28 10:00 | 显示全部楼层
现在我们将按照下列过程展开叙述:

初始化OS--》创建任务--》初始化OS时间单位--》初始化LED灯--》启动OS;

“咦?怎么感觉这些步骤似曾相识呢?”

当然“相识”啦,这个就是main函数的那几行代码的意义啊!快看快看,第一行是OSInit(),这个函数到底做了些什么呢?



(1)初始化OS:

OSInit将完成以下工作:

I.  初始化全局变量OSTCBTbl结构体数组;

II. 创建一个“Idle task”;

III.初始化OSTCBCur和OSTCBNext。
  1. void OSInit(void)
  2. {
  3.     INT8U i;
  4.     OS_ENTER_CRITICAL();
  5.     for(i = 0; i < OS_MAX_TASKS; i++) {
  6.         OSTCBTbl[i].OSTCBStkPtr = (OS_STK*)0;
  7.         OSTCBTbl[i].OSTCBStat = TASK_STATE_CREATING;
  8.     }
  9.     OSInitTaskIdle();
  10.     OSTCBCur = &OSTCBTbl[0];
  11.     OSTCBNext = &OSTCBTbl[0];
  12.     OS_EXIT_CRITICAL();
  13. }
复制代码

首先是第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行,离开临界区,也就是开中断。
 楼主 | 2015-4-28 10:01 | 显示全部楼层
(2)创建任务:

OSTaskCreate会完成以下工作:

I.   找到一个“空闲的”OSTCBTbl;

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)
  4. {
  5. OS_STK * tmp;
  6. INT8U i = 1;
  7. OS_ENTER_CRITICAL();
  8. while(OSTCBTbl[i].OSTCBStkPtr != (OS_STK*)0) {
  9. i++;
  10. }
  11. tmp = OSTaskStkInit(task, p_arg, p_tos);
  12. OSTCBSet(&OSTCBTbl[i], tmp, TASK_STATE_CREATING);
  13. OS_EXIT_CRITICAL();
  14. }
复制代码
 楼主 | 2015-4-28 10:06 | 显示全部楼层
不得不提醒“小白兔们”,现在我们已经来到了这座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),
  2.           void *p_arg,
  3.           OS_STK *p_tos)
  4. {
  5.     OS_STK *stk;
  6.     stk = p_tos;

  7.     *(stk)    = (INT32U)0x01000000L;             // xPSR                                               
  8.     *(--stk)  = (INT32U)task;                    // Entry Point  

  9.     // Don't be serious with the value below. They are of random
  10.     *(--stk)  = (INT32U)0xFFFFFFFEL;             // R14 (LR)
  11.     *(--stk)  = (INT32U)0x12121212L;             // R12                                                
  12.     *(--stk)  = (INT32U)0x03030303L;             // R3                                                
  13.     *(--stk)  = (INT32U)0x02020202L;             // R2                                                
  14.     *(--stk)  = (INT32U)0x01010101L;             // R1                                                

  15.     // pointer of the argument
  16.     *(--stk)  = (INT32U)p_arg;                   // R0

  17.     // Don't be serious with the value below. They are of random
  18.     *(--stk)  = (INT32U)0x11111111L;             // R11
  19.     *(--stk)  = (INT32U)0x10101010L;             // R10
  20.     *(--stk)  = (INT32U)0x09090909L;             // R9  
  21.     *(--stk)  = (INT32U)0x08080808L;             // R8  
  22.     *(--stk)  = (INT32U)0x07070707L;             // R7  
  23.     *(--stk)  = (INT32U)0x06060606L;             // R6  
  24.     *(--stk)  = (INT32U)0x05050505L;             // R5  
  25.     *(--stk)  = (INT32U)0x04040404L;             // R4  
  26.     return stk;
  27. }
复制代码

关键是为何要这样初始化呢?第一次看的话,先从下文找点感觉,看完全部后,还需回来体味体味。

首先,参照《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)0x08080808L;
这都无关紧要,随你怎么设置都行。
最后第30行,函数返回堆栈指针,该指针现在指向地址的内容为0x04040404L,依照注释,就是以后存储R4寄存器内容的地址。

回到 OSTaskCreate函数第12行,它使得先前找到的OSTCBTbl不再“空闲了”。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
 楼主 | 2015-4-28 10:07 | 显示全部楼层
(3)初始化SysTick中断和LED:

在main函数中

SysTickInit(5);
LedInit();

都是与硬件相关的,唯一需要说明的是“SysTickInit(5)”当中的“5”没有太多含义,只是越大,中断发生的时间间隔就越大,具体依照硬件时钟而定。
 楼主 | 2015-4-28 10:13 | 显示全部楼层
(4)main函数最后一行:

OSStart()

该函数一旦执行,将永不返回。注意,接下来我们要和汇编交手了,从汇编部分(os_cpu_a.asm)第31行开始。

这里要请大家注意,每行汇编语言后都有对应的C语言注释,对照着看更容易理解。

第32行:

CPSID I

参照《Cortex-M3权威指南(中文版)》P42,该命令即为关中断,相当于给PRIMASK写“1”。

(注:CPSID指的是属于CPS(Control Processor State)指令的Interrupt Disable指令)

第36~38行:

LDR R0, =NVIC_SYSPRI14

LDR R1, =NVIC_PENDSV_PRI

STRB R1, [R0]

设置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中断,所以下一步我们要开中断啦!

第51行:

CPSIE I

开中断。由于PendSV被触发了,所以接下来CPU跳到PendSV的中断处理函数处执行。也就是os_cpu_a.asm的第59行。

第60行是关中断。

第62~63行:

MRS R0, PSP

CBZ R0, PendSV_Handler_NoSave

比较PSP是否为0,若是0就跳转到PendSV_Handler_NoSave处执行,由于OSStart先前将PSP初始化为0,所以就直接调到PendSV_Handler_NoSave处执行咯。

第104~107行

LDR R0, =OSTCBCur

LDR R1, =OSTCBNext

LDR R2, [R1]

STR R2, [R0]

将OSTCBNext所指向地址的前4个字节,赋给OSTCBCur所指向地址的前4个字节,而这4个字节正是两个指针所指向结构体的OSTCBStkPtr变量!

这段代码做的就是将下个任务的堆栈指针(OSTCBNext)赋给当前任务的堆栈指针(OSTCBCur),因为PendSV中断所做的事情就是进行任务切换。

我们知道,一开始OSTCBCur和OSTCBNext一开始都是指向“Idle Task”的,所以下一个要运行的任务就是“Idle Task”。

第109行:

LDR R0, [R2]

将所要切换的堆栈指针地址赋给R0。

第112~134行:

LDR R4, [R0]

ADD R0, R0, #0x4

LDR R5, [R0]

ADD R0, R0, #0x4

……

LDR R11 , [R0]

ADD R0, R0, #0x4

将堆栈指针R0所指向的堆栈内容赋给R4~R11。问个问题,“这些值都是多少你知道吗?”

“好了,这时候你是不是想问个问题,R0~R3怎么不给它们也赋值呢?”

回到先前那个要大家需要体味的地方

参照《Cortex-M3权威MSR PSP, R0指南(中文版)》P135,表9.1

还有接下来第136行:

MSR PSP, R0

我只说一句:CPU会自动从PSP保存和加载8个量至R0~R3,R12,LR,程序入口(这个是理解的关键)和xPSR,我们无需动手。

第140行:

ORR 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中了。

接下来就是该切换进入新任务了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
 楼主 | 2015-4-28 10:14 | 显示全部楼层
(5)谁来触发PendSV,实现切换任务

如果到此为止,那么CPU只会一直没完没了的执行第一个任务:Idle Task。OSStart确实完成了一次任务切换,但也仅仅就“一次”。

main函数已经没得指望了,它早就撒手不管了,那么谁来再次触发PendSV,从而执行Task1、Task2呢?

别忘了,我们还有一个关键角色没有登场呢:SysTick中断。

让我们回到myos.c的106行的SysTick的中断服务函数:SysTick_Handler。

这个函数逻辑很简单:每发生一次SysTick中断,就将TimeTaskSlice递减,当TimeTaskSlice为0时,就进行切换任务的工作,并将TimeTaskSlice的值还原。

每一次中断发生,TimeMS都会加1,从而记录整个系统的时间。

第111行:

OSTCBCur = OSTCBNext;

将当前任务指针指向下一个任务。

第112行:

OSCtxSw();

这是个汇编实现的函数,在os_cpu_a.asm的第144~150行。

非常简单,它就是在触发PendSV中断!!!

当然,这个触发不会立即发生,因为现在还处于关中断状态。

第113行:

OSTaskNext++;

之前忘记介绍了,这是个全局整型变量,用来记录下一个任务的偏移量的。由于我们的任务没有优先级,只是轮换执行,所以将OSTaskNext向后偏移一个就行了。

但是,如果我们偏移到了最后一个任务怎么办呢?我们得从第一个任务重新开始才是,所以就有了第144~149的循环部分。

第120行:

OSTCBNext = &OSTCBTbl[OSTaskNext];

设置新的下一个要运行的任务的任务指针。

第121行有点多余,不要也行。

第124行开中断,这行命令执行完之后,PendSV就会被触发,接着就是去执行新的任务咯。



至此,关于本OS的关键代码部分就解析完毕了。

真心希望大家多提意见,鄙人将感激涕零!
扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式
我要创建版块 申请成为版主

论坛热帖

快速回复 返回顶部 返回列表