首页 > 其他分享 >HNU-操作系统实验lab6-2022级

HNU-操作系统实验lab6-2022级

时间:2024-10-29 11:50:37浏览次数:7  
标签:TSK taskCb uintptr 任务 HNU lab6 2022 OS struct

实验目的

任务调度是操作系统的核心功能之一。 UniProton实现的是一个单进程支持多线程的操作系统。在UniProton中,一个任务表示一个线程。UniProton中的任务为抢占式调度机制,而非时间片轮转调度方式。高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务挂起或阻塞后才能得到调度。

实验内容

基础数据结构:双向链表

双向链表结构在 src/include/list_types.h 中定义:

#ifndef _LIST_TYPES_H
#define _LIST_TYPES_H

struct TagListObject {
    struct TagListObject *prev;
    struct TagListObject *next;
};

#endif  /* end _LIST_TYPES_H */

这个结构体 TagListObject 包含两个指针成员,prevnext,分别用于指向链表中的前一个和下一个元素。

在 src/include/prt_list_external.h 中定义了链表各种相关操作:

#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)

/* list action low level add */
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;
}

/* list action add */
OS_SEC_ALW_INLINE INLINE void ListAdd(struct TagListObject *newNode, struct TagListObject *listObject)
{
    ListLowLevelAdd(newNode, listObject, listObject->next);
}

/* list action tail add */
OS_SEC_ALW_INLINE INLINE void ListTailAdd(struct TagListObject *newNode, struct TagListObject *listObject)
{
    ListLowLevelAdd(newNode, listObject->prev, listObject);
}

/* list action lowel delete */
OS_SEC_ALW_INLINE INLINE void ListLowLevelDelete(struct TagListObject *prevNode, struct TagListObject *nextNode)
{
    nextNode->prev = prevNode;
    prevNode->next = nextNode;
}

/* list action delete */
OS_SEC_ALW_INLINE INLINE void ListDelete(struct TagListObject *node)
{
    ListLowLevelDelete(node->prev, node->next);

    node->next = NULL;
    node->prev = NULL;
}

/* list action empty */
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)))

/* 根据成员地址得到控制块首地址, ptr成员地址, 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 /* PRT_LIST_EXTERNAL_H */

LIST_OBJECT_INIT(object)

  • 用于初始化一个链表节点 object,使其 prevnext 指针都指向自身。
  • 这是在声明时使用的静态初始化方法。

INIT_LIST_OBJECT(object)

  • 动态初始化一个链表节点 object,使其 prevnext 指针都指向自身。
  • 这是在运行时使用的初始化方法。

LIST_LAST(object)

  • 获取链表 object 的最后一个节点(即 object 的前一个节点)。

LIST_FIRST(object)

  • 获取链表 object 的第一个节点(即 object 的下一个节点)。

ListLowLevelAdd

  • 在两个节点 prevnext 之间添加一个新节点 newNode
  • 这是一个低级别的添加操作,其他添加操作都依赖于它。

ListAdd

  • listObject 节点之后添加 newNode 节点。
  • 实际上调用了 ListLowLevelAdd 函数,将 newNode 插入到 listObjectlistObject->next 之间。

ListTailAdd

  • listObject 节点之前添加 newNode 节点。
  • 实际上调用了 ListLowLevelAdd 函数,将 newNode 插入到 listObject->prevlistObject 之间。

ListLowLevelDelete

  • 从链表中删除节点时,调整 prevNodenextNode 的指针,使它们直接指向对方。
  • 这是一个低级别的删除操作,其他删除操作都依赖于它。

ListDelete

  • 从链表中删除 node 节点,并将其 nextprev 指针置为 NULL

ListEmpty

  • 检查链表 listObject 是否为空(即 nextprev 都指向自身)

OFFSET_OF_FIELD

  • 计算结构体中字段 field 相对于结构体起始地址的偏移量。
  • 使用一个非零基地址(例如 0x10)来避免编译器优化导致的问题。

COMPLEX_OF

  • 根据结构体中的字段指针 ptr 计算得到该字段所属的结构体指针。
  • 使用 OFFSET_OF_FIELD 计算字段在结构体中的偏移量。

LIST_COMPONENT

  • 根据链表节点指针 ptrOfList 计算得到包含该节点的结构体指针。

LIST_FOR_EACH

  • 遍历链表 listObjectposOfList 是循环变量,typeOfList 是包含链表节点的结构体类型,field 是结构体中的链表节点字段。
  • LIST_COMPONENT 用于计算当前节点对应的结构体指针。
  • 遍历从 listObject->next 开始,一直进行到回到 listObject 结束。

LIST_FOR_EACH_SAFE

  • 安全遍历链表 listObject,与 LIST_FOR_EACH 类似,但在遍历过程中增加了对下一个节点是否为空的检查,以确保遍历的安全性。
  • 遍历从 listObject->next 开始,一直进行到回到 listObject 结束,同时检查 posOfList->field.next 是否为 NULL

任务控制块

prt_task.h 中除了一些相关宏定义外,还定义了任务创建时参数传递的结构体: struct TskInitParam:

/*
* 任务创建参数的结构体定义。
*
* 传递任务创建时指定的参数信息。
*/
struct TskInitParam {
    /* 任务入口函数 */
    TskEntryFunc taskEntry;
    /* 任务优先级 */
    TskPrior taskPrio;
    U16 reserved;
    /* 任务参数,最多4个 */
    uintptr_t args[4];
    /* 任务栈的大小 */
    U32 stackSize;
    /* 任务名 */
    char *name;
    /*
    * 本任务的任务栈独立配置起始地址,用户必须对该成员进行初始化,
    * 若配置为0表示从系统内部空间分配,否则用户指定栈起始地址
    */
    uintptr_t stackAddr;
};

prt_task_external.h 中定义了任务调度中最重要的结构——任务控制块 struct TagTskCb:

#define TagOsRunQue TagListObject //简单实现

/*
* 任务线程及进程控制块的结构体统一定义。
*/
struct TagTskCb {
    /* 当前任务的SP */
    void *stackPointer;
    /* 任务状态,后续内部全改成U32 */
    U32 taskStatus;
    /* 任务的运行优先级 */
    TskPrior priority;
    /* 任务栈配置标记 */
    U16 stackCfgFlg;
    /* 任务栈大小 */
    U32 stackSize;
    TskHandle taskPid;

    /* 任务栈顶 */
    uintptr_t topOfStack;

    /* 任务入口函数 */
    TskEntryFunc taskEntry;
    /* 任务Pend的信号量指针 */
    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;
    /* 任务恢复的时间点(单位Tick) */
    U64 expirationTick;

#if defined(OS_OPTION_NUTTX_VFS)
    struct filelist tskFileList;
#if defined(CONFIG_FILE_STREAM)
    struct streamlist ta_streamlist;
#endif
#endif
};

任务控制块(Task Control Block, TCB)是操作系统内核中的一个数据结构,用于存储和管理任务(或线程)的信息。每个任务在创建时都会分配一个任务控制块,操作系统通过这些控制块来管理任务的调度、状态和执行。

任务控制块的作用:

  1. 任务管理:TCB包含了任务的状态、优先级、栈指针等信息,操作系统通过这些信息来管理任务的生命周期。
  2. 任务调度:调度器使用TCB中的优先级和状态信息来决定哪个任务应该被运行。
  3. 任务切换:在任务切换(上下文切换)时,TCB保存和恢复任务的执行上下文,如CPU寄存器、栈指针等。

prt_amp_task_internal.h:

#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 /* PRT_AMP_TASK_INTERNAL_H */

该头文件中主要是定义了三个内联函数,用于将任务控制块加入运行队列头部、加入运行队列尾部和从运行队列中移除任务控制块。

任务创建

相关变量与函数声明
#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"


/* Unused TCBs and ECBs that can be allocated. */
OS_SEC_DATA struct TagListObject g_tskCbFreeList = LIST_OBJECT_INIT(g_tskCbFreeList);

extern U32 OsTskAMPInit(void);
extern U32 OsIdleTskAMPCreate(void);
extern void OsFirstTimeSwitch(void);
极简内存空间管理
//简单实现OsMemAllocAlign
/*
* 描述:分配任务栈空间
* 仅支持4K大小,最多256字节对齐空间的分配
*/
uint8_t stackMem[20][4096] __attribute__((aligned(256))); // 256 字节对齐,20 个 4K 大小的空间
uint8_t stackMemUsed[20] = {0}; // 记录对应 4K 空间是否已被分配
OS_SEC_TEXT void *OsMemAllocAlign(U32 mid, U8 ptNo, U32 size, U8 alignPow)
{
    // 最多支持256字节对齐
    if (alignPow > 8)
        return NULL;
    if (size != 4096)
        return NULL;
    for(int i = 0; i < 20; i++){
        if (stackMemUsed[i] == 0){
            stackMemUsed[i] = 1; // 记录对应 4K 空间已被分配
            return &(stackMem[i][0]); // 返回 4K 空间起始地址
        }
    }
    return NULL;
}

/*
* 描述:分配任务栈空间
*/
OS_SEC_L4_TEXT void *OsTskMemAlloc(U32 size)
{
    void *stackAddr = NULL;
        stackAddr = OsMemAllocAlign((U32)OS_MID_TSK, (U8)0, size,
                                /* 内存已按16字节大小对齐 */
                                OS_TSK_STACK_SIZE_ALLOC_ALIGN);
    return stackAddr;
}
  1. OsMemAllocAlign 函数:
    • 检查 alignPow 是否在允许范围内(最大256字节对齐,因此 alignPow ≤ 8)。
    • 确保请求的大小正好为4096字节。
    • 遍历 stackMemUsed 数组以找到一个未使用的4K块。
    • 标记找到的块为已使用,并返回其起始地址。
    • 如果未找到空闲块,则返回 NULL
  2. OsTskMemAlloc 函数:
    • 使用预定义的任务ID (OS_MID_TSK)、一个虚拟的 ptNo (0)、请求的大小和用于16字节对齐的对齐幂 (OS_TSK_STACK_SIZE_ALLOC_ALIGN,即 4 表示 2^4 = 16 字节) 调用 OsMemAllocAlign
    • 返回分配的栈地址,如果分配失败则返回 NULL
任务栈初始化

在理论课程中,我们知道当发生任务切换时会首先保存前一个任务的上下文到栈里,然后从栈中恢复下一个将运行任务的上下文。可是当任务第一次运行的时候怎么恢复上下文,之前从来没有保存过上下文?

答案就是我们手工制造一个就可以了。下面代码中 stack->x01 到 stack->x29 被初始化成很有标志性意义的值,其他他们的值不重要。比较重要的是 stack->x30 和 stack->spsr 等处的值。

/*
* 描述: 初始化任务栈的上下文
*/
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; // 指针减,减去一个TskContext大小

    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;   // x30: lr(link register)
    stack->xzr = 0;

    stack->elr = funcTskEntry;
    stack->esr = 0;
    stack->far = 0;
    stack->spsr = 0x305;    // EL1_SP1 | D | A | I | F
    return stack;
}

OsTskContextInit 函数

  • 忽略 taskID 参数,因为在当前实现中没有使用它。
  • 通过计算 topStack + stackSize 确定栈顶,并将其转换为 TskContext 结构体的指针。
  • 指针减去一个 TskContext 的大小,预留空间以保存任务的上下文。
  • 初始化 TskContext 结构体中的所有寄存器值,包含通用寄存器、链路寄存器 (x30)、零寄存器 (xzr) 及异常处理相关的寄存器 (elr, esr, far)。
  • funcTskEntry 设置为任务入口函数地址,并将其分配给 x30elr
  • 设置 spsr (保存的程序状态寄存器) 为 0x305,表示进入 EL1 模式,屏蔽 DAIF 中断。

struct TskContext:

/*
* 任务上下文的结构体定义。
*/
struct TskContext {
    /* *< 当前物理寄存器R0-R12 */
    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;
};
任务入口函数
/*
* 描述:所有任务入口
*/
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;

    /* PRT_TaskDelete不能关中断操作,否则可能会导致它核发SGI等待本核响应时死等 */
    OsIntRestore(intSave);

    OsTaskExit(taskCb);
}

OsTskEntry 函数:

  • 获取当前运行的任务控制块 taskCb
  • 调用任务的入口函数 taskEntry,并传递任务参数 args 数组中的值。
  • 关闭中断并保存当前中断状态,防止在关键操作期间中断干扰。
  • 重置任务锁数据。
  • 恢复中断状态,以确保后续操作的正常进行。
  • 调用 OsTaskExit 函数,退出当前任务。
创建任务
// src/core/kernel/task/prt_task_internal.h
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;
}

OS_SEC_ALW_INLINE INLINE bool OsCheckAddrOffsetOverflow(uintptr_t base, size_t size)
{
    return (base + size) < base;
}

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;
}

OS_SEC_L4_TEXT void OsTskCreateTcbInit(uintptr_t stackPtr, struct TskInitParam *initParam,
    uintptr_t topStackAddr, uintptr_t curStackSize, struct TagTskCb *taskCb)
{
    /* Initialize the task's stack */
    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;
}

/*
* 描述:创建一个任务但不进行激活
*/
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;
    // 出参ID传出
    *taskPid = taskId;
    OsIntRestore(intSave);
    return OS_OK;
}

/*
* 描述:创建一个任务但不进行激活
*/
OS_SEC_L4_TEXT U32 PRT_TaskCreate(TskHandle *taskPid, struct TskInitParam *initParam)
{
    return OsTaskCreateOnly(taskPid, initParam);
}
  1. OsTaskCreateChkAndGetTcb 函数

    检查任务控制块(TCB)空闲列表是否为空。如果为空,返回错误码 OS_ERRNO_TSK_TCB_UNAVAILABLE。如果不为空,从空闲列表中获取一个 TCB,删除该 TCB,并返回 OS_OK

  2. OsCheckAddrOffsetOverflow 函数

    检查地址加偏移是否溢出。如果溢出,返回 true;否则返回 false

  3. OsTskCreateTcbInit 函数

    初始化任务控制块(TCB),设置任务的栈指针、参数、优先级、任务入口函数等。初始化任务的信号量阻塞列表、挂起列表和定时器列表。

  4. OsTaskCreateOnly函数

    先关闭中断并保存中断状态。获取一个空闲的任务控制块。分配任务栈资源。如果分配失败,将任务控制块重新加入空闲列表,恢复中断状态并返回错误码。

    初始化任务栈,设置任务的上下文,就像刚发生中断一样。

    初始化任务控制块。

    恢复中断状态。

  5. PRT_TaskCreate函数

    调用 OsTaskCreateOnly 函数创建一个任务但不进行激活。

解挂任务

PRT_TaskResume 函数负责解挂任务,即将 Suspend 状态的任务转换到就绪状态。PRT_TaskResume 首先检查当前任务是否已创建且处于 Suspend 状态,如果处于 Suspend 状态,则清除 Suspend 位,然后调用 OsMoveTaskToReady 将任务控制块移到就绪队列中。

OsMoveTaskToReady 函数将任务加入就绪队列 g_runQueue,然后通过 OsTskSchedule 进行任务调度和切换(稍后描述)。 由于有新的任务就绪,所以需要通过OsTskSchedule 进行调度。这个位置一般称为调度点。对于优先级调度来说,找到所有的调度点并进行调度非常重要。

// src/core/kernel/task/prt_task_internal.h
OS_SEC_ALW_INLINE INLINE void OsMoveTaskToReady(struct TagTskCb *taskCb)
{
    if (TSK_STATUS_TST(taskCb, OS_TSK_DELAY_INTERRUPTIBLE)) {
        /* 可中断delay, 属于定时等待的任务时候,去掉其定时等待标志位*/
        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 task is not blocked then move it to ready list */
    if ((taskCb->taskStatus & OS_TSK_BLOCK) == 0) {
        OsTskReadyAdd(taskCb);

        if ((OS_FLG_BGD_ACTIVE & UNI_FLAG) != 0) {
            OsTskSchedule();
            return;
        }
    }
}

/*
* 描述解挂任务
*/
OS_SEC_L2_TEXT U32 PRT_TaskResume(TskHandle taskPid)
{
    uintptr_t intSave;
    struct TagTskCb *taskCb = NULL;

    // 获取 taskPid 对应的任务控制块
    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 task is not suspended and not in interruptible delay then return */
    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);

    /* If task is not blocked then move it to ready list */
    OsMoveTaskToReady(taskCb);
    OsIntRestore(intSave);

    return OS_OK;
}
  1. OsMoveTaskToReady函数

    处理处于 OS_TSK_DELAY_INTERRUPTIBLE状态的任务。

    如果任务处于定时等待状态(OS_TSK_TIMEOUT),解除其定时等待。

    清除任务的 OS_TSK_TIMEOUTOS_TSK_DELAY_INTERRUPTIBLE 状态。

    如果任务未被阻塞(OS_TSK_BLOCK),将其移动到就绪列表。

    如果系统处于后台激活状态,触发任务调度。

  2. PRT_TaskResume函数

    恢复挂起的任务。

    如果任务未被创建或处于活动状态且系统任务锁定,返回相应的错误码。

    如果任务未挂起或不处于可中断延迟状态,返回错误码。

    清除任务的挂起状态,将其移动到就绪列表。

任务管理系统初始化与启动
/*
* 描述:AMP任务初始化
*/
extern U32 g_threadNum;
extern void *OsMemAllocAlign(U32 mid, U8 ptNo, U32 size, U8 alignPow);
OS_SEC_L4_TEXT U32 OsTskAMPInit(void)
{
    uintptr_t size;
    U32 idx;

    // 简单处理,分配4096,存OS_MAX_TCB_NUM个任务。#define OS_MAX_TCB_NUM  (g_tskMaxNum + 1 + 1)  // 1个IDLE,1个无效任务
    g_tskCbArray = (struct TagTskCb *)OsMemAllocAlign((U32)OS_MID_TSK, 0,
                                                    4096, OS_TSK_STACK_SIZE_ALLOC_ALIGN);
    if (g_tskCbArray == NULL) {
        return OS_ERRNO_TSK_NO_MEMORY;
    }

    g_tskMaxNum = 4096 / sizeof(struct TagTskCb) - 2;


    // 1为Idle任务
    g_threadNum += (g_tskMaxNum + 1);

    // 初始化为全0
    for(int i = 0; i < OS_MAX_TCB_NUM - 1; i++)
        g_tskCbArray[i] = {0};

    g_tskBaseId = 0;

    // 将所有控制块加入g_tskCbFreeList链表,且设置控制块的初始状态和任务id
    INIT_LIST_OBJECT(&g_tskCbFreeList);
    for (idx = 0; idx < OS_MAX_TCB_NUM - 1; idx++) {
        g_tskCbArray[idx].taskStatus = OS_TSK_UNUSED;
        g_tskCbArray[idx].taskPid = (idx + g_tskBaseId);
        ListTailAdd(&g_tskCbArray[idx].pendList, &g_tskCbFreeList);
    }

    /* 在初始化时给RUNNING_TASK的PID赋一个合法的无效值,放置在Trace使用时出现异常 */
    RUNNING_TASK = OS_PST_ZOMBIE_TASK;

    /* 在初始化时给RUNNING_TASK的PID赋一个合法的无效值,放置在Trace使用时出现异常 */
    RUNNING_TASK->taskPid = idx + g_tskBaseId;

    INIT_LIST_OBJECT(&g_runQueue);

    /* 增加OS_TSK_INUSE状态,使得在Trace记录的第一条信息状态为OS_TSK_INUSE(创建状态) */
    RUNNING_TASK->taskStatus = (OS_TSK_INUSE | OS_TSK_RUNNING);
    RUNNING_TASK->priority = OS_TSK_PRIORITY_LOWEST + 1;

    return OS_OK;
}

/*
* 描述:任务初始化
*/
OS_SEC_L4_TEXT U32 OsTskInit(void)
{
    U32 ret;
    ret = OsTskAMPInit();
    if (ret != OS_OK) {
        return ret;
    }

    return OS_OK;
}

/*
* 描述:Idle背景任务
*/
OS_SEC_TEXT void OsTskIdleBgd(void)
{
    while (TRUE);
}

/*
* 描述:ilde任务创建.
*/
OS_SEC_L4_TEXT U32 OsIdleTskAMPCreate(void)
{
    U32 ret;
    TskHandle taskHdl;
    struct TskInitParam taskInitParam = {0};
    char tskName[OS_TSK_NAME_LEN] = "IdleTask";

    /* Create background task. */
    taskInitParam.taskEntry = (TskEntryFunc)OsTskIdleBgd;
    taskInitParam.stackSize = 4096;
    // taskInitParam.name = tskName;
    taskInitParam.taskPrio = OS_TSK_PRIORITY_LOWEST;
    taskInitParam.stackAddr = 0;

    /* 任务调度的必要条件就是有背景任务,此时背景任务还没有创建,因此不会发生任务切换 */
    ret = PRT_TaskCreate(&taskHdl, &taskInitParam);
    if (ret != OS_OK) {
        return ret;
    }
    ret = PRT_TaskResume(taskHdl);
    if (ret != OS_OK) {
        return ret;
    }
    IDLE_TASK_ID = taskHdl;

    return ret;
}

/*
* 描述:激活任务管理
*/
OS_SEC_L4_TEXT U32 OsActivate(void)
{
    U32 ret;
    ret = OsIdleTskAMPCreate();
    if (ret != OS_OK) {
        return ret;
    }

    OsTskHighestSet();

    /* Indicate that background task is running. */
    UNI_FLAG |= OS_FLG_BGD_ACTIVE | OS_FLG_TSK_REQ;

    /* Start Multitasking. */
    OsFirstTimeSwitch();
    // 正常情况不应执行到此
    return OS_ERRNO_TSK_ACTIVE_FAILED;
}
  1. OsTskAMPInit函数

    任务控制块(TCB)数组内存并初始化。

    将所有任务控制块加入空闲列表。

    初始化运行中的任务。

  2. OsTskInit函数

    调用 OsTskAMPInit 初始化任务管理系统。

  3. OsTskIdleBgd函数

    定义空闲任务,进入无限循环。当系统没有其他任务需要运行时,会执行空闲任务IDLE

  4. OsIdleTskAMPCreate函数

    设置任务初始化参数。

    调用 PRT_TaskCreate 创建空闲任务。

    调用 PRT_TaskResume 恢复空闲任务。

    保存空闲任务句柄 IDLE_TASK_ID

  5. OsActivate函数

    创建空闲任务并启动任务管理系统。

    主要步骤:

    1. 调用 OsIdleTskAMPCreate 创建空闲任务。
    2. 调用 OsTskHighestSet 设置最高优先级任务。
    3. 设置后台任务和任务请求标志 UNI_FLAG
    4. 调用 OsFirstTimeSwitch 启动多任务调度。
    5. 返回错误码(不应执行到此,出现错误)。

任务状态转换

#include "prt_task_external.h"
#include "prt_typedef.h"
#include "os_attr_armv8_external.h"
#include "prt_asm_cpu_external.h"
#include "os_cpu_armv8_external.h"
#include "prt_amp_task_internal.h"

OS_SEC_BSS struct TagOsRunQue g_runQueue;  // 核的局部运行队列

/*
* 描述:将任务添加到就绪队列, 调用者确保不会换核,并锁上rq
*/
OS_SEC_L0_TEXT void OsTskReadyAdd(struct TagTskCb *task)
{
    struct TagOsRunQue *rq = &g_runQueue;
    TSK_STATUS_SET(task, OS_TSK_READY);

    OS_TSK_EN_QUE(rq, task, 0);
    OsTskHighestSet();

    return;
}

/*
* 描述:将任务从就绪队列中移除,关中断外部保证
*/
OS_SEC_L0_TEXT void OsTskReadyDel(struct TagTskCb *taskCb)
{
    struct TagOsRunQue *runQue = &g_runQueue;
    TSK_STATUS_CLEAR(taskCb, OS_TSK_READY);

    OS_TSK_DE_QUE(runQue, taskCb, 0);
    OsTskHighestSet();

    return;
}

// src/core/kernel/task/prt_task_del.c
/*
* 描述:任务结束退出
*/
OS_SEC_L4_TEXT void OsTaskExit(struct TagTskCb *tsk)
{

    uintptr_t intSave = OsIntLock();

    OsTskReadyDel(tsk);
    OsTskSchedule();

    OsIntRestore(intSave);

}
  1. OsTskReadyAdd函数

    将任务添加到就绪队列。

    主要步骤:

    1. 获取运行队列 rq
    2. 设置任务状态为 OS_TSK_READY
    3. 将任务加入就绪队列 OS_TSK_EN_QUE
    4. 调用 OsTskHighestSet 更新最高优先级任务。
  2. OsTskReadyDel函数

    将任务从就绪队列中移除。

    主要步骤:

    1. 获取运行队列 runQue
    2. 清除任务状态中的 OS_TSK_READY
    3. 将任务从就绪队列移除 OS_TSK_DE_QUE
    4. 调用 OsTskHighestSet 更新最高优先级任务。
  3. OsTaskExit函数

    处理任务退出操作。

    主要步骤:

    1. 关中断并保存中断状态 OsIntLock
    2. 将任务从就绪队列中移除 OsTskReadyDel
    3. 调用 OsTskSchedule 进行任务调度。
    4. 恢复中断状态 OsIntRestore

调度与切换

prt_sched_single.c:

#include "prt_task_external.h"
#include "os_attr_armv8_external.h"
#include "prt_asm_cpu_external.h"
#include "os_cpu_armv8_external.h"

/*
* 描述:任务调度,切换到最高优先级任务
*/
OS_SEC_TEXT void OsTskSchedule(void)
{
    /* 外层已经关中断 */
    /* Find the highest task */
    OsTskHighestSet();

    /* In case that running is not highest then reschedule */
    if ((g_highestTask != RUNNING_TASK) && (g_uniTaskLock == 0)) {
        UNI_FLAG |= OS_FLG_TSK_REQ;

        /* only if there is not HWI or TICK the trap */
        if (OS_INT_INACTIVE) { // 不在中断上下文中,否则应该在中断返回时切换
            OsTaskTrap();
            return;
        }
    }

    return;
}

/*
* 描述: 调度的主入口
* 备注: NA
*/
OS_SEC_L0_TEXT void OsMainSchedule(void)
{
    struct TagTskCb *prevTsk;
    if ((UNI_FLAG & OS_FLG_TSK_REQ) != 0) {
        prevTsk = RUNNING_TASK;

        /* 清除OS_FLG_TSK_REQ标记位 */
        UNI_FLAG &= ~OS_FLG_TSK_REQ;

        RUNNING_TASK->taskStatus &= ~OS_TSK_RUNNING;
        g_highestTask->taskStatus |= OS_TSK_RUNNING;

        RUNNING_TASK = g_highestTask;
    }
    // 如果中断没有驱动一个任务ready,直接回到被打断的任务
    OsTskContextLoad((uintptr_t)RUNNING_TASK);
}

/*
* 描述: 系统启动时的首次任务调度
* 备注: NA
*/
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);
    // never get here
    return;
}
  1. OsTskSchedule函数

    执行任务调度,切换到最高优先级任务。

    主要步骤:

    1. 调用 OsTskHighestSet 找到最高优先级任务。
    2. 如果当前运行的任务不是最高优先级任务且任务未锁定,设置 OS_FLG_TSK_REQ 标志。
    3. 如果不在中断上下文中,调用 OsTaskTrap 执行任务切换。
  2. OsMainSchedule函数

    调度的主入口,切换到最高优先级任务。

    主要步骤:

    1. 检查 OS_FLG_TSK_REQ 标志。
    2. 如果标志被设置,执行任务切换:
      • 清除 OS_FLG_TSK_REQ 标志。
      • 更新当前任务和最高优先级任务的状态。
      • 切换到最高优先级任务。
    3. 调用 OsTskContextLoad 恢复任务上下文。
  3. OsFirstTimeSwitch函数

    系统启动时的首次任务调度。

    主要步骤:

    1. 调用 OsTskHighestSet 找到最高优先级任务。
    2. 设置当前运行任务为最高优先级任务并更新其状态。
    3. 调用 OsTskContextLoad 加载最高优先级任务的上下文。

OsTskHighestSet 函数在 src/include/prt_task_external.h 中被定义:

/*
* 模块内内联函数定义
*/
OS_SEC_ALW_INLINE INLINE void OsTskHighestSet(void)
{
    struct TagTskCb *taskCb = NULL;
    struct TagTskCb *savedTaskCb = NULL;

    // 遍历g_runQueue队列,查找优先级最高的任务
    LIST_FOR_EACH(taskCb, &g_runQueue, struct TagTskCb, pendList) {
        // 第一个任务,直接保存到savedTaskCb
        if(savedTaskCb == NULL) {
            savedTaskCb = taskCb;
            continue;
        }
        // 比较优先级,值越小优先级越高
        if(taskCb->priority < savedTaskCb->priority){
            savedTaskCb = taskCb;
        }
    }

    g_highestTask = savedTaskCb;
}

功能:

  • 遍历运行队列 g_runQueue,找到优先级最高的任务,并将其指针保存在 g_highestTask 中。

主要步骤:

  • 使用宏 LIST_FOR_EACH 遍历 g_runQueue 中的所有任务控制块。这个宏通常定义为一个循环,遍历链表中的每个元素。
  • 如果 savedTaskCbNULL,表示这是第一个遍历到的任务,直接将其保存到 savedTaskCb
  • 对于后续的每个任务,比较其优先级(taskCb->priority)与当前保存的最高优先级任务的优先级(savedTaskCb->priority)。
  • 如果当前任务的优先级更高(值越小优先级越高),则更新 savedTaskCb 为当前任务。
  • 遍历完成后,将 savedTaskCb 中保存的最高优先级任务指针赋值给全局变量 g_highestTask

在 src/bsp/prt_vector.S 实现 OsTskContextLoad,OsContextLoad 和 OsTaskTrap:

/*
* 描述: void OsTskContextLoad(uintptr_t stackPointer)
*/
    .globl OsTskContextLoad
    .type OsTskContextLoad, @function
    .align 4
OsTskContextLoad:
    ldr    X0, [X0]
    mov    SP, X0            // X0 is stackPointer

OsContextLoad:
    ldp    x2, x3, [sp],#16
    add    sp, sp, #16        // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr
    msr    spsr_el1, x3
    msr    elr_el1, x2
    dsb    sy
    isb

    RESTORE_EXC_REGS // 恢复上下文

    eret //从异常返回


/*
* 描述: Task调度处理函数。 X0 is g_runningTask
*/
    .globl OsTaskTrap
    .type OsTaskTrap, @function
    .align 4

OsTaskTrap:
    LDR    x1, =g_runningTask /* OsTaskTrap是函数调用过来,x0 x1寄存器是caller save,此处能直接使用 */
    LDR    x0, [x1] /* x0 is the &g_pRunningTask->sp */

    SAVE_EXC_REGS

    /* TskTrap需要保存CPSR,由于不能直接访问,需要拼接获取当前CPSR入栈 */
    mrs    x3, DAIF /* CPSR:DAIF 4种事件的mask, bits[9:6] */
    mrs    x2, NZCV /* NZCV:Condition flags, bits[31:28] */
    orr    x3, x3, x2
    orr    x3, x3, #(0x1U << 2) /* 当前的 exception level,bits[3:2] 00:EL0,01:El1,10:El2,11:EL3 */
    orr    x3, x3, #(0x1U) /* 当前栈的选择,bits[0] 0:SP_EL0,1:SP_ELX */

    mov    x2, x30    // 用返回地址x30作为现场恢复点
    sub    sp, sp, #16  // 跳过esr_el1, far_el1, 异常时才有用
    stp    x2, x3, [sp,#-16]!

    // 存入SP指针到g_pRunningTask->sp
    mov    x1, sp
    str    x1, [x0]   // x0 is the &g_pRunningTask->sp

    B      OsMainSchedule
loop1:
    B      loop1
  • OsTskContextLoad 函数加载任务上下文,恢复 CPU 寄存器的状态,并通过 eret 指令从异常返回。
  • OsTaskTrap 函数保存当前任务的上下文,并调用 OsMainSchedule 进行任务调度,选择下一个任务。

任务调度测试

image-20241029114616853

作业

实现分时调度

分时调度有多种,这里我选择的是轮转调度。我们主要修改的是调度与切换模块的prt_sched_single.c文件。

  • 添加ListPopAndPushFirst函数
/*
 * 描述: 从队列中取出第一个元素,然后插入到队列的最后
 */
OS_SEC_ALW_INLINE INLINE void ListPopAndPushFirst(struct TagListObject *listObject)
{
    struct TagTskCb *taskCb = NULL;
    taskCb = LIST_FIRST_ELEM(listObject, struct TagTskCb, pendList);
    OsDequeueTaskAmp(&g_runQueue, taskCb);
    OsEnqueueTaskAmp(&g_runQueue, taskCb);
}

这个函数用于从队列中取出第一个元素,然后将其插入到队列的最后。OsDequeueTaskAmp用于删除队列第一个元素的位置, OsEnqueueTaskAmp用于将原先的第一个元素加到队尾。

  • 添加 OsTskFIFOSet函数
/*
 * 描述: 根据FIFO调度算法设置当前任务
 */
OS_SEC_ALW_INLINE INLINE void OsTskFIFOSet(void)
{
    struct TagTskCb *nextTaskCb = NULL;
    nextTaskCb = LIST_FIRST_ELEM(&g_runQueue, struct TagTskCb, pendList);
    if (nextTaskCb->taskPid == g_idleTaskId)
    {
        ListPopAndPushFirst(&g_runQueue);
        nextTaskCb = LIST_FIRST_ELEM(&g_runQueue, struct TagTskCb, pendList);
    }
    g_highestTask = nextTaskCb;
}

这个函数的主要目的是根据FIFO调度算法设置当前任务。它首先从运行队列中获取第一个任务,如果这个任务是空闲任务,那么它会将这个任务移动到队列的末尾,并设置下一个任务为当前任务。

  • 添加OsTimerInterrupt函数
/*
 * 描述: 定时器中断处理函数
 */
OS_SEC_TEXT void OsTimerInterrupt(void)
{
    OsGicIntClear(30); // 必须清除中断,否则系统会一直处于中断状态,就无法再次进入中断
    if ((OS_FLG_BGD_ACTIVE & UNI_FLAG) == 0)
    {
        return;
    }
   
    if (PRT_TickGetCount() - taskStartTick > OS_TASK_TIME_SLICE_TICKS)
    {
        if ((RUNNING_TASK->taskStatus & OS_TSK_READY) != 0)
        { // Fix the comparison operator
            ListPopAndPushFirst(&g_runQueue);
        }
        OsTskSchedule();
    }
}

这个函数是定时器中断处理函数。当定时器中断发生时,它会清除中断,并检查是否需要进行任务调度。如果从上次任务开始到现在的时间超过了任务的时间片,那么它会将当前任务移动到运行队列的末尾,并调用OsTskSchedule进行任务调度。将其放在时钟处理函数中:

OS_SEC_TEXT void OsTickDispatcher(void)
{
    uintptr_t intSave;
    intSave = OsIntLock();
    g_uniTicks++;
    U64 cycle = g_timerFrequency / OS_TICK_PER_SECOND;
    OS_EMBED_ASM("MSR CNTP_TVAL_EL0, %0" : : "r"(cycle) : "memory", "cc"); // 设置中断周期
    OsTimerInterrupt();
    OsIntRestore(intSave);
}
  • 修改 OsMainSchedule函数
/*
 * 描述: 调度的主入口
 * 备注: NA
 */
OS_SEC_L0_TEXT void OsMainSchedule(void)
{
    struct TagTskCb *prevTsk;
    if ((UNI_FLAG & OS_FLG_TSK_REQ) != 0)
    {
        prevTsk = RUNNING_TASK;

        /* 清除OS_FLG_TSK_REQ标记位 */
        UNI_FLAG &= ~OS_FLG_TSK_REQ;

        RUNNING_TASK->taskStatus &= ~OS_TSK_RUNNING;
        g_highestTask->taskStatus |= OS_TSK_RUNNING;

        RUNNING_TASK = g_highestTask;

        taskStartTick = PRT_TickGetCount();
    }
    // 如果中断没有驱动一个任务ready,直接回到被打断的任务
    OsTskContextLoad((uintptr_t)RUNNING_TASK);
}

添加了一句taskStartTick = PRT_TickGetCount();,用于更新任务开始的时间点,便于时间处理函数中对运行时间与时间片的比较。

  • 修改OsFirstTimeSwitch函数
/*
 * 描述: 系统启动时的首次任务调度
 * 备注: NA
 */
OS_SEC_L4_TEXT void OsFirstTimeSwitch(void)
{
    OsTskFIFOSet();
    RUNNING_TASK = g_highestTask;
    taskStartTick = PRT_TickGetCount();
    TSK_STATUS_SET(RUNNING_TASK, OS_TSK_RUNNING);
    OsTskContextLoad((uintptr_t)RUNNING_TASK);
    // never get here
    return;
}

修改启动时调用的以FIFO算法为基础的调用函数。

同时在main函数中,添加:

void Delay(U64 delay_ms)
{
    U64 end_time = PRT_TickGetCount() + delay_ms;
    while (PRT_TickGetCount() < end_time)
        ;
}

void Test1TaskEntry()
{
    PRT_Printf("task 1 run \n");

    U32 cnt = 5;
    while (cnt > 0)
    {
        Delay(200);
        PRT_Printf("task 1 run \n");
        cnt--;
    }
}

void Test2TaskEntry()
{
    PRT_Printf("task 2 run \n");

    U32 cnt = 5;
    while (cnt > 0)
    {
        Delay(200);
        PRT_Printf("task 2 run \n");
        cnt--;
    }
}

Delay函数用于在指定的毫秒数内阻塞当前任务。它通过一个while循环和一个计时器实现,直到达到指定的延迟时间。

运行验证结果:

image-20241029114804693

标签:TSK,taskCb,uintptr,任务,HNU,lab6,2022,OS,struct
From: https://blog.csdn.net/2301_76658831/article/details/143325287

相关文章

  • C++之OpenCV入门到提高001:使用 Visual Studio2022 配置 OpenCV 环境
    一、介绍从今天开始,我们又要开始一个新的系列了,这个系列就是《C++之Opencv入门到提高》。这个系列是有关如何使用C++语言,通过Opencv来实现图像处理、缺陷检测、视频处理、机器学习等功能。OpenCV我也是新接触的,一步一步的学习,一步一步提高。这个系列是以C++为基......
  • 如何减小VS2022 MAUI生成的apk文件大小?
    在解决方案目录树中双击解决方案,打开对应的文档(也就是解决方案名称.csproj文件)的编辑界面在<PropertyGroup>节点内添加以下内容:<!--缩小发布的.apk文件大小--><AndroidLinkTool>r8</AndroidLinkTool><AndroidEnableResourceShrinking>true</AndroidEnableResourceShrinking><An......
  • ROS2在MyCobot320Pi2022和PC之间通讯(都是Ubuntu20.04系统)
    参考:RunningROSacrossmultiplemachineshttp://wiki.ros.org/ROS/Tutorials/MultipleMachinesSLAM+语音机器人DIY系列:(五)树莓派3开发环境搭建——4.PC端与robot端ROS网络通信https://www.cnblogs.com/hiram-zhang/p/10410168.html在多台PC上进行ROS通讯(在多台远程机器人......
  • 20222304 2024-2025-1 《网络与系统攻防技术》实验三实验报告
    实验内容1.1实践内容正确使用msf编码器,veil-evasion,自己利用shellcode编程等免杀工具或技巧使用msfvenom生成jar、apk等其他文件使用veil加壳工具使用C+shellcode进行编程通过组合应用各种技术实现恶意代码免杀用另一电脑实测,在杀软开启的情况下,可运行并回连成功,注明电......
  • 20222303 2024-2025-1 《网络与系统攻防技术》实验三实验报告
    一、实验内容1.正确使用msf编码器,使用msfvenom生成如jar之类的其他文件;2.能够使用veil,加壳工具;3.能够使用C+shellcode编程;4.能够通过组合应用各种技术实现恶意代码免杀;5.用另一电脑实测,在杀软开启的情况下,可运行并回连成功,注明电脑的杀软名称与版本。二、基础问题回答1.杀软......
  • 20222425 2024-2025-1 《网络与系统攻防技术》实验三实验报告
    202224252024-2025-1《网络与系统攻防技术》实验三实验报告目录1.实验内容2.实验问题3.实验过程3.1正确使用msf编码器,veil-evasion,自己利用shellcode编程等免杀工具或技巧3.2通过组合应用各种技术实现恶意代码免杀3.3用另一电脑实测,在杀软开启的情况下,可运行并回连成功,......
  • HNU-操作系统实验Lab5-2022级
    实验目的深刻理解中断的原理和机制,掌握CPU访问中断控制器的方法,掌握Arm体系结构的中断机制和规范,实现时钟中断服务和部分异常处理等。实验过程前言中断是一种硬件机制。借助于中断,CPU可以不必再采用轮询这种低效的方式访问外部设备。将所有的外部设备与CPU直接相连是不......
  • Windows Server 2022 中文版、英文版下载 (updated Oct 2024)
    WindowsServer2022中文版、英文版下载(updatedOct2024)WindowsServer2022x64,Version21H2请访问原文链接:https://sysin.org/blog/windows-server-2022/查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgWindowsServer2022采用先进的多层安全机制......
  • Windows Server 2022 中文版、英文版下载 (updated Oct 2024)
    WindowsServer2022中文版、英文版下载(updatedOct2024)WindowsServer2022x64,Version21H2请访问原文链接:https://sysin.org/blog/windows-server-2022/查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgWindowsServer2022采用先进的多层安全机制、Azure......
  • 20222408 2024-2025-1 《网络与系统攻防技术》实验三实验报告
    1.实验内容1.1回答问题(1)杀软是如何检测出恶意代码的?①基于特征码的检测:AV软件厂商搜集最全最新的特征码库,并以此来尝试匹配文件中的一个或几个片段②启发式恶意软件检测:根据片面特征推断,包括行为(如连接恶意网站、开放端口、修改系统文件等),外观(文件签名、结构、厂商等)。③基于行......