Lab 6 :任务调度
任务调度是操作系统的核心功能之一。 UniProton实现的是一个==单进程支持多线程==的操作系统。在UniProton中,一个任务表示一个线程。UniProton中的任务为==抢占式调度机制==,而非时间片轮转调度方式。高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务挂起或阻塞后才能得到调度。
基础数据结构:双向链表
双向链表结构在 src/include/list_types.h 中定义。
1 2 3 4 5 6 7 8 9
| #ifndef _LIST_TYPES_H #define _LIST_TYPES_H
struct TagListObject { struct TagListObject *prev; struct TagListObject *next; };
#endif
|
此外,在 src/include/prt_list_external.h 中定义了链表各种相关操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #ifndef PRT_LIST_EXTERNAL_H #define PRT_LIST_EXTERNAL_H
#include "prt_typedef.h" #include "list_types.h"
#define LIST_OBJECT_INIT(object) { \ &(object), &(object) \ }
#define INIT_LIST_OBJECT(object) \ do { \ (object)->next = (object); \ (object)->prev = (object); \ } while (0)
#define LIST_LAST(object) ((object)->prev) #define LIST_FIRST(object) ((object)->next) #define OS_LIST_FIRST(object) ((object)->next)
OS_SEC_ALW_INLINE INLINE void ListLowLevelAdd(struct TagListObject *newNode, struct TagListObject *prev, struct TagListObject *next) { newNode->next = next; newNode->prev = prev; next->prev = newNode; prev->next = newNode; }
OS_SEC_ALW_INLINE INLINE void ListAdd(struct TagListObject *newNode, struct TagListObject *listObject) { ListLowLevelAdd(newNode, listObject, listObject->next); }
OS_SEC_ALW_INLINE INLINE void ListTailAdd(struct TagListObject *newNode, struct TagListObject *listObject) { ListLowLevelAdd(newNode, listObject->prev, listObject); }
OS_SEC_ALW_INLINE INLINE void ListLowLevelDelete(struct TagListObject *prevNode, struct TagListObject *nextNode) { nextNode->prev = prevNode; prevNode->next = nextNode; }
OS_SEC_ALW_INLINE INLINE void ListDelete(struct TagListObject *node) { ListLowLevelDelete(node->prev, node->next);
node->next = NULL; node->prev = NULL; }
OS_SEC_ALW_INLINE INLINE bool ListEmpty(const struct TagListObject *listObject) { return (bool)((listObject->next == listObject) && (listObject->prev == listObject)); }
#define OFFSET_OF_FIELD(type, field) ((uintptr_t)((uintptr_t)(&((type *)0x10)->field) - (uintptr_t)0x10))
#define COMPLEX_OF(ptr, type, field) ((type *)((uintptr_t)(ptr) - OFFSET_OF_FIELD(type, field)))
#define LIST_COMPONENT(ptrOfList, typeOfList, fieldOfList) COMPLEX_OF(ptrOfList, typeOfList, fieldOfList)
#define LIST_FOR_EACH(posOfList, listObject, typeOfList, field) \ for ((posOfList) = LIST_COMPONENT((listObject)->next, typeOfList, field); &(posOfList)->field != (listObject); \ (posOfList) = LIST_COMPONENT((posOfList)->field.next, typeOfList, field))
#define LIST_FOR_EACH_SAFE(posOfList, listObject, typeOfList, field) \ for ((posOfList) = LIST_COMPONENT((listObject)->next, typeOfList, field); \ (&(posOfList)->field != (listObject))&&((posOfList)->field.next != NULL); \ (posOfList) = LIST_COMPONENT((posOfList)->field.next, typeOfList, field))
#endif
|
这里面最有意思的是 LIST_COMPONENT 宏,其作用是根据成员地址得到控制块首地址, ptr成员地址, type控制块结构, field成员名。
LIST_FOR_EACH 和 LIST_FOR_EACH_SAFE 用于遍历链表,主要是简化代码编写。
[!NOTE] 例子
struct Person {
int age;
struct list_node node; // 链表节点
};
// 通过节点指针找到包含它的Person结构
struct list_node* someNode = …;
struct Person* person = LIST_COMPONENT(someNode, struct Person, node);
任务控制块
在 include目录下创建 src/include/prt_task.h src/include/prt_task_external.h
prt_task.h 中定义了任务创建时参数传递的结构体: struct TskInitParam。
prt_task_external.h 中定义了任务调度中最重要的数据结构——任务控制块 struct TagTskCb。
将任务运行队列结构 TagOsRunQue 直接定义为双向链表 TagListObject。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #define TagOsRunQue TagListObject
struct TagTskCb { void *stackPointer; U32 taskStatus; TskPrior priority; U16 stackCfgFlg; U32 stackSize; TskHandle taskPid;
uintptr_t topOfStack;
TskEntryFunc taskEntry; void *taskSem;
uintptr_t args[4]; #if (defined(OS_OPTION_TASK_INFO)) char name[OS_TSK_NAME_LEN]; #endif struct TagListObject pendList; struct TagListObject timerList; struct TagListObject semBList; struct TagListObject condNode; #if defined(OS_OPTION_LINUX) struct TagListObject waitList; #endif #if defined(OS_OPTION_EVENT) U32 event; U32 eventMask; #endif U32 lastErr; U64 expirationTick;
#if defined(OS_OPTION_NUTTX_VFS) struct filelist tskFileList; #if defined(CONFIG_FILE_STREAM) struct streamlist ta_streamlist; #endif #endif };
|
创建 src/include/prt_module.h src/include/prt_errno.h
prt_module.h中主要是一些模块ID的定义,而 prt_errno.h 主要是错误类型的相关定义,引入这两个头文件主要是为了保持接口与原版 UniProton 相一致。
创建 src/include/prt_amp_task_internal.h
定义了三个内联函数,将任务控制块加入运行队列或者从运行运行队列中移除任务控制块,用到了我们在上面定义的双向链表的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #ifndef PRT_AMP_TASK_INTERNAL_H #define PRT_AMP_TASK_INTERNAL_H
#include "prt_task_external.h" #include "prt_list_external.h"
#define OS_TSK_EN_QUE(runQue, tsk, flags) OsEnqueueTaskAmp((runQue), (tsk))
#define OS_TSK_EN_QUE_HEAD(runQue, tsk, flags) OsEnqueueTaskHeadAmp((runQue), (tsk))
#define OS_TSK_DE_QUE(runQue, tsk, flags) OsDequeueTaskAmp((runQue), (tsk))
extern U32 OsTskAMPInit(void); extern U32 OsIdleTskAMPCreate(void);
OS_SEC_ALW_INLINE INLINE void OsEnqueueTaskAmp(struct TagOsRunQue *runQue, struct TagTskCb *tsk) { ListTailAdd(&tsk->pendList, runQue); return; }
OS_SEC_ALW_INLINE INLINE void OsEnqueueTaskHeadAmp(struct TagOsRunQue *runQue, struct TagTskCb *tsk) { ListAdd(&tsk->pendList, runQue); return; }
OS_SEC_ALW_INLINE INLINE void OsDequeueTaskAmp(struct TagOsRunQue *runQue, struct TagTskCb *tsk) { ListDelete(&tsk->pendList); return; }
#endif
|
任务创建
以下代码若无代码块外的特殊声明,都是写在 src/kernel/task/prt_task_init.c 中
相关变量与函数声明
首先是引入必要的头文件。
然后声明了 1 个全局双向链表g_tskCbFreeList,并通过 LIST_OBJECT_INIT 宏进行初始化。 g_tskCbFreeList 链表是空闲的任务控制块链表。
最后声明了3个外部函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include "list_types.h" #include "os_attr_armv8_external.h" #include "prt_list_external.h" #include "prt_task.h" #include "prt_task_external.h" #include "prt_asm_cpu_external.h" #include "os_cpu_armv8_external.h" #include "prt_config.h"
OS_SEC_DATA struct TagListObject g_tskCbFreeList = LIST_OBJECT_INIT(g_tskCbFreeList);
extern U32 OsTskAMPInit(void); extern U32 OsIdleTskAMPCreate(void); extern void OsFirstTimeSwitch(void);
|
其中头文件 src/include/prt_asm_cpu_external.h [下载] 包含内核相关的一些状态定义。

极简内存空间管理
内核运行过程中需要动态分配内存。我们实现了一种极简的内存管理,该内存管理方法仅支持4K大小,最多256字节对齐空间的分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
uint8_t stackMem[20][4096] __attribute__((aligned(256))); uint8_t stackMemUsed[20] = {0}; OS_SEC_TEXT void *OsMemAllocAlign(U32 mid, U8 ptNo, U32 size, U8 alignPow) { if (alignPow > 8) return NULL; if (size != 4096) return NULL; for(int i = 0; i < 20; i++){ if (stackMemUsed[i] == 0){ stackMemUsed[i] = 1; return &(stackMem[i][0]); } } return NULL; }
OS_SEC_L4_TEXT void *OsTskMemAlloc(U32 size) { void *stackAddr = NULL; stackAddr = OsMemAllocAlign((U32)OS_MID_TSK, (U8)0, size, OS_TSK_STACK_SIZE_ALLOC_ALIGN); return stackAddr; }
|
[!info]
OsMemAllocAlign 函数用于分配任务栈空间。它接受四个参数:
mid:用于标识内存的类型。
ptNo:保留参数,未被使用。
size:期望的分配大小,这里固定为 4096 字节(4K)。
alignPow:对齐的幂次,最多支持 256 字节对齐,即对齐幂次为8。
OsTskMemAlloc 函数是一个简单的封装函数,用于分配任务栈空间。它调用OsMemAllocAlign 函数来获取任务栈空间,其中 OS_TSK_STACK_SIZE_ALLOC_ALIGN是一个
预定义的值,代表已经按照 16 字节大小对齐。返回分配空间的起始地址
任务栈初始化
我们手工制造一个任务栈就可以了。下面代码中 stack->x01 到 stack->x29 被初始化成很有标志性意义的值,其他他们的值不重要。比较重要的是 stack->x30 和 stack->spsr 等处的值。
struct TskContext 表示任务上下文,放在 src/bsp/os_cpu_armv8.h 中定义。在我们的实现上它与中断上下文 struct ExcRegInfo (在 src/bsp/os_exc_armv8.h 中定义)没有区别。在UniProton中,它们的定义有一些差别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
void *OsTskContextInit(U32 taskID, U32 stackSize, uintptr_t *topStack, uintptr_t funcTskEntry) { (void)taskID; struct TskContext *stack = (struct TskContext *)((uintptr_t)topStack + stackSize);
stack -= 1;
stack->x00 = 0; stack->x01 = 0x01010101; stack->x02 = 0x02020202; stack->x03 = 0x03030303; stack->x04 = 0x04040404; stack->x05 = 0x05050505; stack->x06 = 0x06060606; stack->x07 = 0x07070707; stack->x08 = 0x08080808; stack->x09 = 0x09090909; stack->x10 = 0x10101010; stack->x11 = 0x11111111; stack->x12 = 0x12121212; stack->x13 = 0x13131313; stack->x14 = 0x14141414; stack->x15 = 0x15151515; stack->x16 = 0x16161616; stack->x17 = 0x17171717; stack->x18 = 0x18181818; stack->x19 = 0x19191919; stack->x20 = 0x20202020; stack->x21 = 0x21212121; stack->x22 = 0x22222222; stack->x23 = 0x23232323; stack->x24 = 0x24242424; stack->x25 = 0x25252525; stack->x26 = 0x26262626; stack->x27 = 0x27272727; stack->x28 = 0x28282828; stack->x29 = 0x29292929; stack->x30 = funcTskEntry; stack->xzr = 0;
stack->elr = funcTskEntry; stack->esr = 0; stack->far = 0; stack->spsr = 0x305; return stack;
}
|
[!info]
void *OsTskContextInit(U32 taskID, U32 stackSize, uintptr_t *topStack, uintptr_t funcTskEntry)
- taskID: 任务ID(当前未使用)
- stackSize: 栈大小
- topStack: 栈顶指针
- funcTskEntry: 任务入口函数地址
- 返回值: 初始化后的栈指针位置
- 此处重要的是寄存器x30将传入的funcTskEntry设置为该任务入口地址,spsr寄存器(异常发生时的程序状态))
在 src/bsp/os_cpu_armv8.h 中加入 struct TskContext 定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
struct TskContext { uintptr_t elr; uintptr_t spsr; uintptr_t far; uintptr_t esr; uintptr_t xzr; uintptr_t x30; uintptr_t x29; uintptr_t x28; uintptr_t x27; uintptr_t x26; uintptr_t x25; uintptr_t x24; uintptr_t x23; uintptr_t x22; uintptr_t x21; uintptr_t x20; uintptr_t x19; uintptr_t x18; uintptr_t x17; uintptr_t x16; uintptr_t x15; uintptr_t x14; uintptr_t x13; uintptr_t x12; uintptr_t x11; uintptr_t x10; uintptr_t x09; uintptr_t x08; uintptr_t x07; uintptr_t x06; uintptr_t x05; uintptr_t x04; uintptr_t x03; uintptr_t x02; uintptr_t x01; uintptr_t x00; };
|
任务入口函数
OsTskEntry 所有任务的入口函数
这个函数有几个有趣的地方。
(1)==你找不到类似 OsTskEntry(taskId); 这样的对 OsTskEntry 的函数调用。这实际上是在通过 OsTskContextInit 函数进行栈初始化时传入的,也就意味着当任务第一次就绪运行时会进入 OsTskEntry 执行。==
(2)用户指定的 taskcb->taskEntry 不一定要求是 4 参数的,可以是 0~4 参数之间任意选定,这个需要你在汇编层面去理解。
采用 OsTskEntry 的好处是在用户提供的 taskCb->taskEntry 函数的基础上进行了一层封装,比如可以确保调用taskCb->taskEntry执行完后调用 OsTaskExit,回收系统资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
OS_SEC_L4_TEXT void OsTskEntry(TskHandle taskId) { struct TagTskCb *taskCb; uintptr_t intSave; (void)taskId; taskCb = RUNNING_TASK;
taskCb->taskEntry(taskCb->args[OS_TSK_PARA_0], taskCb->args[OS_TSK_PARA_1], taskCb->args[OS_TSK_PARA_2], taskCb->args[OS_TSK_PARA_3]);
intSave = OsIntLock();
OS_TASK_LOCK_DATA = 0;
OsIntRestore(intSave); OsTaskExit(taskCb);
}
|
[!info]
任务执行流程:
- 所有任务都通过这个统一入口启动
- 实际任务代码通过
taskEntry
指针调用
- 支持最多4个参数传递
中断处理:
- 使用
OsIntLock
/OsIntRestore
保护临界区
- 注释指出调度器会处理中断状态,所以这里只是确保
OS_TASK_LOCK_DATA
的原子修改
创建任务
接口函数 PRT_TaskCreate
接口函数 PRT_TaskCreate 函数根据传入的 initParam 参数创建任务返回任务句柄 taskPid。
1 2 3 4 5 6 7
|
OS_SEC_L4_TEXT U32 PRT_TaskCreate(TskHandle *taskPid, struct TskInitParam *initParam) { return OsTaskCreateOnly(taskPid, initParam); }
|
PRT_TaskCreate 函数会直接调用 OsTaskCreateOnly 函数实际进行任务创建。
OsTaskCreateOnly 任务创建但不直接运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| OS_SEC_L4_TEXT U32 OsTaskCreateOnly(TskHandle *taskPid, struct TskInitParam *initParam) { U32 ret; U32 taskId; uintptr_t intSave; uintptr_t *topStack = NULL; void *stackPtr = NULL; struct TagTskCb *taskCb = NULL; uintptr_t curStackSize = 0;
intSave = OsIntLock(); ret = OsTaskCreateChkAndGetTcb(&taskCb); if (ret != OS_OK) { OsIntRestore(intSave); return ret; }
taskId = taskCb->taskPid; ret = OsTaskCreateRsrcInit(taskId, initParam, taskCb, &topStack, &curStackSize); if (ret != OS_OK) { ListAdd(&taskCb->pendList, &g_tskCbFreeList); OsIntRestore(intSave); return ret; } stackPtr = OsTskContextInit(taskId, curStackSize, topStack, (uintptr_t)OsTskEntry); OsTskCreateTcbInit((uintptr_t)stackPtr, initParam, (uintptr_t)topStack, curStackSize, taskCb);
taskCb->taskStatus = OS_TSK_SUSPEND | OS_TSK_INUSE; *taskPid = taskId; OsIntRestore(intSave); return OS_OK;
}
|
OsTaskCreateOnly 函数将:
通过 OsTaskCreateChkAndGetTcb 函数从空闲链表 g_tskCbFreeList 中取一个任务控制块;
在 OsTaskCreateRsrcInit 函数中,如果用户未提供堆栈空间,则通过 OsTskMemAlloc 为新建的任务分配堆栈空间;
OsTskContextInit 函数负责将栈初始化成刚刚发生过中断一样;
OsTskCreateTcbInit 函数负责用 initParam 参数等初始化任务控制块,包括栈指针、入口函数、优先级和参数等;
最后将任务的状态设置为挂起 Suspend 状态。这意味着 PRT_TaskCreate 创建任务后处于 Suspend 状态,而不是就绪状态。
OsTaskCreateChkAndGetTcb 从tcb空闲列表中获取一个任务控制块
1 2 3 4 5 6 7 8 9 10 11 12 13
| OS_SEC_ALW_INLINE INLINE U32 OsTaskCreateChkAndGetTcb(struct TagTskCb **taskCb) { if (ListEmpty(&g_tskCbFreeList)) { return OS_ERRNO_TSK_TCB_UNAVAILABLE; }
*taskCb = GET_TCB_PEND(OS_LIST_FIRST(&g_tskCbFreeList)); ListDelete(OS_LIST_FIRST(&g_tskCbFreeList)); return OS_OK; }
|
OsTaskCreateRsrcInit 为任务分配堆栈空间
initParam 结构体用于传参,指定该任务创建的参数
topStackOut 指向一个指针的指针,用于输出任务栈的栈顶地址
curStackSize 指向一个无符号整型变量的指针,用于输出当前任务栈的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| OS_SEC_L4_TEXT U32 OsTaskCreateRsrcInit(U32 taskId, struct TskInitParam *initParam, struct TagTskCb *taskCb,uintptr_t **topStackOut, uintptr_t *curStackSize) { U32 ret = OS_OK; uintptr_t *topStack = NULL;
if (initParam->stackAddr != 0) { topStack = (void *)(initParam->stackAddr); taskCb->stackCfgFlg = OS_TSK_STACK_CFG_BY_USER; } else { topStack = OsTskMemAlloc(initParam->stackSize); if (topStack == NULL) { ret = OS_ERRNO_TSK_NO_MEMORY; } else { taskCb->stackCfgFlg = OS_TSK_STACK_CFG_BY_SYS; } } *curStackSize = initParam->stackSize; if (ret != OS_OK) { return ret; }
*topStackOut = topStack; return OS_OK;
}
|
OsTskContextInit 初始化任务栈的上下文
函数解释在任务栈初始化,这里不再赘述
OsTskCreateTcbInit 负责用 initParam 参数等初始化tcb
stackPtr 指向任务栈的指针
initParam 指向初始化参数的指针
topStackAddr 任务栈顶地址
curStackSize 当前任务栈大小
taskCb 指向任务tcb的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| OS_SEC_L4_TEXT void OsTskCreateTcbInit(uintptr_t stackPtr, struct TskInitParam *initParam, uintptr_t topStackAddr, uintptr_t curStackSize, struct TagTskCb *taskCb) { taskCb->stackPointer = (void *)stackPtr; taskCb->args[OS_TSK_PARA_0] = (uintptr_t)initParam->args[OS_TSK_PARA_0]; taskCb->args[OS_TSK_PARA_1] = (uintptr_t)initParam->args[OS_TSK_PARA_1]; taskCb->args[OS_TSK_PARA_2] = (uintptr_t)initParam->args[OS_TSK_PARA_2]; taskCb->args[OS_TSK_PARA_3] = (uintptr_t)initParam->args[OS_TSK_PARA_3]; taskCb->topOfStack = topStackAddr; taskCb->stackSize = curStackSize; taskCb->taskSem = NULL; taskCb->priority = initParam->taskPrio; taskCb->taskEntry = initParam->taskEntry; #if defined(OS_OPTION_EVENT) taskCb->event = 0; taskCb->eventMask = 0; #endif taskCb->lastErr = 0; INIT_LIST_OBJECT(&taskCb->semBList); INIT_LIST_OBJECT(&taskCb->pendList); INIT_LIST_OBJECT(&taskCb->timerList); return; }
|
解挂任务
PRT_TaskResume 函数 负责解挂任务
负责解挂任务,即将 Suspend 状态的任务转换到就绪状态。
PRT_TaskResume 首先检查当前任务是否已创建且处于 Suspend 状态,如果处于 Suspend 状态,则清除 Suspend 位,然后调用 OsMoveTaskToReady 将任务控制块移到就绪队列中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| OS_SEC_L2_TEXT U32 PRT_TaskResume(TskHandle taskPid) { uintptr_t intSave; struct TagTskCb *taskCb = NULL;
taskCb = GET_TCB_HANDLE(taskPid); intSave = OsIntLock(); if (TSK_IS_UNUSED(taskCb)) { OsIntRestore(intSave); return OS_ERRNO_TSK_NOT_CREATED; }
if (((OS_TSK_RUNNING & taskCb->taskStatus) != 0) && (g_uniTaskLock != 0)) { OsIntRestore(intSave); return OS_ERRNO_TSK_ACTIVE_FAILED; }
if (((OS_TSK_SUSPEND | OS_TSK_DELAY_INTERRUPTIBLE) & taskCb->taskStatus) == 0) { OsIntRestore(intSave); return OS_ERRNO_TSK_NOT_SUSPENDED; } TSK_STATUS_CLEAR(taskCb, OS_TSK_SUSPEND);
OsMoveTaskToReady(taskCb); OsIntRestore(intSave);
return OS_OK;
}
|
OsMoveTaskToReady 将任务加入就绪队列 g_runQueue
然后通过 OsTskSchedule 进行任务调度和切换(稍后描述)。 由于有新的任务就绪,所以需要通过OsTskSchedule 进行调度。这个位置一般称为调度点。对于优先级调度来说,找到所有的调度点并进行调度非常重要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| OS_SEC_ALW_INLINE INLINE void OsMoveTaskToReady(struct TagTskCb *taskCb) { if (TSK_STATUS_TST(taskCb, OS_TSK_DELAY_INTERRUPTIBLE)) { if (TSK_STATUS_TST(taskCb, OS_TSK_TIMEOUT)) { OS_TSK_DELAY_LOCKED_DETACH(taskCb); } TSK_STATUS_CLEAR(taskCb, OS_TSK_TIMEOUT | OS_TSK_DELAY_INTERRUPTIBLE); } if ((taskCb->taskStatus & OS_TSK_BLOCK) == 0) { OsTskReadyAdd(taskCb); if ((OS_FLG_BGD_ACTIVE & UNI_FLAG) != 0) { OsTskSchedule(); return; } } }
|
任务管理系统初始化与启动
OsTskInit 函数通过调用 OsTskAMPInit 函数完成任务管理系统的初始化。主要包括:
为任务控制块分配空间,由于我们只实现了简单的内存分配算法,所以支持的任务控制块数目为:4096 / sizeof(struct TagTskCb) - 2; 减去2是因为预留了 1 个空闲任务, 1 个无效任务。
将所有分配的任务控制块加入空闲任务控制块链表 g_tskCbFreeList, 并对所有控制块进行初始化。
任务就绪链表 g_runQueue 通过 INIT_LIST_OBJECT 初始化为空。
RUNNING_TASK 目前指向无效任务。
OsActivate 启动多任务系统。
首先通过 OsIdleTskAMPCreate 函数创建空闲任务,这样当系统中没有其他任务就绪时就可以执行空闲任务了。
OsTskHighestSet 函数在就绪队列中查找最高优先级任务并将 g_highestTask 指针指向该任务。
UNI_FLAG 设置好内核状态
OsFirstTimeSwitch 函数将会加载 g_highestTask 的上下文后执行(稍后描述)。


在 prt_config.h 中加入空闲任务优先级定义。
1
| #define OS_TSK_PRIORITY_LOWEST 63
|
函数功能分析与总结
1. OsTskAMPInit()
2. OsTskInit()
3. OsTskIdleBgd()
4. OsIdleTskAMPCreate()
5. OsActivate()
任务状态切换
在 src/kernel/task/prt_task.c 中,
声明了运行队列 g_runQueue, 注意我们之前已经将其定义为双向队列。
提供了将任务添加到就绪队列的 OsTskReadyAdd 函数和从就绪队列中移除就绪队列的 OsTskReadyDel 函数。
OsTskReadyAdd 会设置任务为就绪态
- 首先获取全局运行队列g_runQueue,然后设置任务的状态为就绪 (OS_TSK_READY),并把任务添加到运行队列中,最后调用OsTskHighestSet()将g_highestTask 指针指向最高优先级任务(每当就绪队列中的任务发生变化时,要重新找到当前最高优先级的任务)。
OsTskReadyDel 会清除任务的就绪态
- 首先获取全局运行队列g_runQueue,然后清除任务的就绪状态 (OS_TSK_READY),并从运行队列中移除该任务。最后,它同样调用OsTskHighestSet()将g_highestTask 指针指向最高优先级任务。
提供了任务结束退出 OsTaskExit 函数,注意 OsTskEntry 中会调用 OsTaskExit 函数。由于任务退出,因此需要进行调度,即存在调度点,所以调用 OsTskSchedule 函数。
- 其首先锁定中断(防止退出过程引发中断),然后调用OsTskReadyDel()将任务从就绪队列中移除,最后调用OsTskSchedule()进行任务调度(因为一个任务运行结束之后,需要陷入操作系统来引发调度),最后恢复中断。

其中,OS_TSK_EN_QUE 和 OS_TSK_DE_QUE 宏在 src/include/prt_amp_task_internal.h 定义。
调度与切换
src/kernel/sched/prt_sched_single.c
OsTskSchedule 任务调度,切换到最高优先级任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| OS_SEC_TEXT void OsTskSchedule(void) { OsTskHighestSet();
if ((g_highestTask != RUNNING_TASK) && (g_uniTaskLock == 0)) { UNI_FLAG |= OS_FLG_TSK_REQ;
if (OS_INT_INACTIVE) { OsTaskTrap(); return; } }
return; }
|
OsMainSchedule 调度的主入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| OS_SEC_L0_TEXT void OsMainSchedule(void) { struct TagTskCb *prevTsk; if ((UNI_FLAG & OS_FLG_TSK_REQ) != 0) { prevTsk = RUNNING_TASK; UNI_FLAG &= ~OS_FLG_TSK_REQ;
RUNNING_TASK->taskStatus &= ~OS_TSK_RUNNING; g_highestTask->taskStatus |= OS_TSK_RUNNING;
RUNNING_TASK = g_highestTask; } OsTskContextLoad((uintptr_t)RUNNING_TASK); }
|
OsFirstTimeSwitch 系统启动时的首次任务调度
1 2 3 4 5 6 7 8 9
| OS_SEC_L4_TEXT void OsFirstTimeSwitch(void) { OsTskHighestSet(); RUNNING_TASK = g_highestTask; TSK_STATUS_SET(RUNNING_TASK, OS_TSK_RUNNING); OsTskContextLoad((uintptr_t)RUNNING_TASK); return; }
|
OsTskHighestSet 设置 g_highestTask 为最高优先级任务
在 src/include/prt_task_external.h 中被定义为内联函数,提高性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| OS_SEC_ALW_INLINE INLINE void OsTskHighestSet(void) { struct TagTskCb *taskCb = NULL; struct TagTskCb *savedTaskCb = NULL;
LIST_FOR_EACH(taskCb, &g_runQueue, struct TagTskCb, pendList) { if(savedTaskCb == NULL) { savedTaskCb = taskCb; continue; } if(taskCb->priority < savedTaskCb->priority){ savedTaskCb = taskCb; } } g_highestTask = savedTaskCb; }
|
实现 OsTskContextLoad,OsContextLoad 和 OsTaskTrap
在 src/bsp/prt_vector.S 实现 OsTskContextLoad,OsContextLoad 和 OsTaskTrap。
OsTskContextLoad 从任务的栈指针(stackPointer
)恢复任务上下文,并执行 eret
返回任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
.globl OsTskContextLoad .type OsTskContextLoad, .align 4 OsTskContextLoad: ldr X0, [X0] mov SP, X0
OsContextLoad: ldp x2, x3, [sp],#16 add sp, sp, #16 msr spsr_el1, x3 msr elr_el1, x2 dsb sy isb
RESTORE_EXC_REGS
eret
|
OsTaskTrap 保存当前任务上下文,并跳转到 OsMainSchedule
进行任务切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
.globl OsTaskTrap .type OsTaskTrap, .align 4
OsTaskTrap: LDR x1, =g_runningTask LDR x0, [x1]
SAVE_EXC_REGS
mrs x3, DAIF mrs x2, NZCV orr x3, x3, x2 orr x3, x3, #(0x1U << 2) orr x3, x3, #(0x1U) mov x2, x30 sub sp, sp, #16 stp x2, x3, [sp,#-16]!
mov x1, sp str x1, [x0]
B OsMainSchedule loop1: B loop1
|
剩下的一点7788
在 src/bsp/os_cpu_armv8_external.h 加入 OsTaskTrap 和 OsTskContextLoad 的声明和关于栈地址和大小对齐宏。
1 2 3 4 5 6 7 8 9
| #define OS_TSK_STACK_SIZE_ALIGN 16U #define OS_TSK_STACK_SIZE_ALLOC_ALIGN 4U #define OS_TSK_STACK_ADDR_ALIGN 16U
extern void OsTaskTrap(void); extern void OsTskContextLoad(uintptr_t stackPointer);
|
最后在 src/kernel/task/prt_sys.c 定义了内核的各种全局数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include "prt_typedef.h" #include "os_attr_armv8_external.h" #include "prt_task.h"
OS_SEC_L4_BSS U32 g_threadNum;
OS_SEC_DATA U32 g_uniFlag = 0;
OS_SEC_DATA struct TagTskCb *g_runningTask = NULL;
OS_SEC_BSS TskEntryFunc g_tskIdleEntry;
OS_SEC_BSS U32 g_tskMaxNum; OS_SEC_BSS struct TagTskCb *g_tskCbArray; OS_SEC_BSS U32 g_tskBaseId;
OS_SEC_BSS TskHandle g_idleTaskId; OS_SEC_BSS U16 g_uniTaskLock; OS_SEC_BSS struct TagTskCb *g_highestTask;
|
任务调度测试
运行测试程序

作业
实现分时调度
时间片轮转调度
修改任务调度函数OsTskSchedule,将OsTskHighestSet()改为OsTskRR(),fifo对就绪队列进行处理,最高优先级任务设为队首元素

OsTskRR() prt_task_external.h
这里写的比较笨,大概意思就是用fifo策略更新队列,调度队首元素

调度点:一定的tick过后触发调度,显然我们可以在时钟中断处理函数中加一些操作
我们在OsTickDispatcher中新增OsTimerInterrupt();

新增定时器中断处理函数OsTimerInterrupt(); prt_fifo.c
每当我们的tick增加一个时间片长度,就进行进程切换,这里要注意,在任务队列为空时,我们不执行任务调度,否则会异常。

定义时间片长度

修改main.c使得效果更加明显
Delay函数用于在指定的毫秒数内阻塞当前任务。它通过一个while循环和一个计时器实现,直到达到指定的延迟时间。

- 输出如下

相当不错呀~