多优先级任务

本文探讨了嵌入式实时操作系统(RTOS)中多优先级任务的管理方法,包括设计目标、优先级原理及其实现细节。通过添加优先级字段、位图表并修改任务初始化、延迟和系统节拍处理等函数,实现了高效的多任务调度。

1 多优先级任务

1.1 设计目标

问题引入:

  • 任务数量无限,而资源有限。当多个任务在等待同一资源/事件时,如果资源/事件不能在任务间共享,应该有哪个任务来处理?
    在这里插入图片描述

1.2 优先级原理

在此我们只讨论CPU占用的优先级:

  • RTOS维护一个就绪表,每个表项对应一个任务,对应一种优先级。就绪表指明哪些优先级的任务等待占用CPU运行。
  • 为便于快速找到优先级更高的任务运行,使用了就绪位置标记就绪,快速查找。
    在这里插入图片描述
    在这里插入图片描述

1.3 设计实现

添加优先级字段:
在这里插入图片描述
添加优先级位图表:
在这里插入图片描述
添加获取当前最高优先级任务的函数:

/**********************************************************************************************************
** Function name        :   tTaskHighestReady
** Descriptions         :   获取当前最高优先级且可运行的任务
** parameters           :   无
** Returned value       :   优先级最高的且可运行的任务
***********************************************************************************************************/
tTask * tTaskHighestReady (void) 
{
    uint32_t highestPrio = tBitmapGetFirstSet(&taskPrioBitmap);
    return taskTable[highestPrio];
}

修改任务初始化函数:

/**********************************************************************************************************
** Function name        :   tTaskInit
** Descriptions         :   初始化任务结构
** parameters           :   task        要初始化的任务结构
** parameters           :   entry       任务的入口函数
** parameters           :   param       传递给任务的运行参数
** Returned value       :   无
***********************************************************************************************************/
void tTaskInit (tTask * task, void (*entry)(void *), void *param, uint32_t prio, uint32_t * stack)
{
    // 为了简化代码,tinyOS无论是在启动时切换至第一个任务,还是在运行过程中在不同间任务切换
    // 所执行的操作都是先保存当前任务的运行环境参数(CPU寄存器值)的堆栈中(如果已经运行运行起来的话),然后再
    // 取出从下一个任务的堆栈中取出之前的运行环境参数,然后恢复到CPU寄存器
    // 对于切换至之前从没有运行过的任务,我们为它配置一个“虚假的”保存现场,然后使用该现场恢复。

    // 注意以下两点:
    // 1、不需要用到的寄存器,直接填了寄存器号,方便在IDE调试时查看效果;
    // 2、顺序不能变,要结合PendSV_Handler以及CPU对异常的处理流程来理解
    *(--stack) = (unsigned long)(1<<24);                // XPSR, 设置了Thumb模式,恢复到Thumb状态而非ARM状态运行
    *(--stack) = (unsigned long)entry;                  // 程序的入口地址
    *(--stack) = (unsigned long)0x14;                   // R14(LR), 任务不会通过return xxx结束自己,所以未用
    *(--stack) = (unsigned long)0x12;                   // R12, 未用
    *(--stack) = (unsigned long)0x3;                    // R3, 未用
    *(--stack) = (unsigned long)0x2;                    // R2, 未用
    *(--stack) = (unsigned long)0x1;                    // R1, 未用
    *(--stack) = (unsigned long)param;                  // R0 = param, 传给任务的入口函数
    *(--stack) = (unsigned long)0x11;                   // R11, 未用
    *(--stack) = (unsigned long)0x10;                   // R10, 未用
    *(--stack) = (unsigned long)0x9;                    // R9, 未用
    *(--stack) = (unsigned long)0x8;                    // R8, 未用
    *(--stack) = (unsigned long)0x7;                    // R7, 未用
    *(--stack) = (unsigned long)0x6;                    // R6, 未用
    *(--stack) = (unsigned long)0x5;                    // R5, 未用
    *(--stack) = (unsigned long)0x4;                    // R4, 未用

    task->stack = stack;                                // 保存最终的值
    task->delayTicks = 0;
    task->prio = prio;                                  // 设置任务的优先级

    taskTable[prio] = task;                             // 填入任务优先级表
    tBitmapSet(&taskPrioBitmap, prio);                  // 标记优先级位置中的相应位
}

修改tTaskDelay函数:

/**********************************************************************************************************
** Function name        :   tTaskDelay
** Descriptions         :   使当前任务进入延时状态。
** parameters           :   delay 延时多少个ticks
** Returned value       :   无
***********************************************************************************************************/
void tTaskDelay (uint32_t delay) {
   // 配置好当前要延时的ticks数
    uint32_t status = tTaskEnterCritical();
    currentTask->delayTicks = delay;
    tBitmapClear(&taskPrioBitmap, currentTask->prio);
    tTaskExitCritical(status);

    // 然后进行任务切换,切换至另一个任务,或者空闲任务
    // delayTikcs会在时钟中断中自动减1.当减至0时,会切换回来继续运行。
    tTaskSched();
}

修改tTaskSystemTickHandler函数:

/**********************************************************************************************************
** Function name        :   tTaskSystemTickHandler
** Descriptions         :   系统时钟节拍处理。
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
void tTaskSystemTickHandler () 
{
    // 检查所有任务的delayTicks数,如果不0的话,减1。
    int i;   
    uint32_t status = tTaskEnterCritical();

    for (i = 0; i < TINYOS_PRO_COUNT; i++) 
    {
        if (taskTable[i]->delayTicks > 0)
        {
            taskTable[i]->delayTicks--;
        }
        else 
        {
            tBitmapSet(&taskPrioBitmap, i);
        }
    }
    tTaskExitCritical(status);

    // 这个过程中可能有任务延时完毕(delayTicks = 0),进行一次调度。
    tTaskSched();
}

修改调度算法:

/**********************************************************************************************************
** Function name        :   tTaskSched
** Descriptions         :   任务调度接口。tinyOS通过它来选择下一个具体的任务,然后切换至该任务运行。
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
void tTaskSched (void) 
{   
    tTask * tempTask;

    // 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
    uint32_t status = tTaskEnterCritical();

    // 如何调度器已经被上锁,则不进行调度,直接退bm
    if (schedLockCount > 0) 
    {
        tTaskExitCritical(status);
        return;
    }

    // 找到优先级最高的任务,如果其优先级比当前任务的还高,那么就切换到这个任务
    tempTask = tTaskHighestReady();
    if (tempTask != currentTask) 
    {
        nextTask = tempTask;
        tTaskSwitch();   
    }

    // 退出临界区
    tTaskExitCritical(status); 
}

参考资料:

  1. 【李述铜】从0到1自己动手写嵌入式操作系统
任务多线程管理模块,任务优先级,一个任务执行完毕,按照优先级高低执行另一个任务 1)如何使用: 1. 声明一个HashTaskList,或在栈上动态获取; 2. 调用InitTaskList初始化上一步的HashTaskList 3. 调用StartTaskManager开始启动任务管理 ***** 对于一个HashTaskList而言,以上2个函数只调用一次 4. 调用ApplyTask向第一步得到的结构对象添加任务,每调用一次添一个任务 2)如何终止任务管理功能: 1. 程序结束,终止程序既停止管理并且释放所有资源,请看2).2括号的内容 2. 调用某个函数,这个功能未写(考虑到模块作为服务运行,不需要终止任务管理的函数) 需要取消所有1v1管理线程,在此需要一个列表保存这些线程的ID 需要清理表当中的互斥锁和信号变量,如果1).1.是动态分配,要释放 3)关于改进: 1. 回收资源的线程和TaskList reclaim[LEVEL_ARRAY_LIMIT];是可以省掉的 但考虑到模块功能的延伸: 如果任务执行失败,先移动到某个地方,过段时间在放回来重试,等等... 其实还有很多可能用到它的地方...所以,依旧保留 2. 有多少个任务可以并发管理目前是固定的 其实可添加一个对StartTaskManager所生成的线程管理的数据结构 这样就能动态的增加或减少任务管理的1v1线程 ***** 可事实上我并不想做这个管理的东西在里面 ***** 没有必要,对提供服务的程序而言这东西一般是固定不变的 ********************************************* 1) How to use: 1. Declare a HashTaskList, or dynamic access stack; 2. Call the initialization of step HashTaskList InitTaskList
<think> 我们参考了用户提供的三个引用,并结合用户需求“使用C语言实现多优先级任务队列调度器”进行分析: 引用[1]提供了优先级队列的基本操作(初始化、判空、入队、删除最高优先级元素、获取最高优先级元素),这是实现任务调度器的基础数据结构。 引用[2]是关于优先级调度算法的实现,其中提到了进程的优先级、到达时间、执行时间等属性,以及需要记录的调度信息(等待时间、响应时间等)。 引用[3]则提到了多级反馈队列调度算法,其中展示了一个进程控制块的结构(包含进程名、优先级、轮转时间片、已用CPU时间、需要时间、状态、计数器等)。 用户需求是“多优先级任务队列调度器”,即支持多个优先级级别的任务队列,通常每个优先级一个队列,调度时选择最高优先级任务执行。 设计思路: 1. 定义任务(进程)控制块(TCB/PCB)结构体,包含任务属性(如任务ID、优先级、到达时间、需要执行时间、已执行时间、状态等)。 2. 实现多个优先级队列,每个队列是一个优先级队列(如使用最大堆或链表)。通常,高优先级队列优先被调度。 3. 调度器维护多个优先级队列(如用数组或链表管理这些队列)。 4. 调度时,从最高优先级非空队列中选取队首任务执行。 5. 考虑是否支持时间片轮转:可在每个优先级内部使用轮转调度;或者像多级反馈队列那样,任务不同优先级队列间移动。 根据用户引用[3]的多级反馈队列,我们也可以考虑实现多级反馈队列调度算法(MLFQ):任务进入系统时放在最高优先级队列,如果时间片用完还没执行完,则降低优先级到下一级队列。这样,短任务可以快速完成,长任务也不会饿死。 但用户明确要求“多优先级任务队列调度器”,我们可以先实现基本的固定优先级多队列调度器(每个任务优先级固定,不会改变),然后可以扩展为多级反馈队列。 步骤: 1. 定义任务结构体(参考引用[2][3]): - 任务ID、名称、优先级、到达时间、需要运行时间、已运行时间、状态(就绪、运行、完成等)、其他统计信息(开始时间、完成时间、等待时间等) 2. 定义队列结构体(每个优先级一个队列): - 可以使用一个数组,每个元素是一个队列(链表实现),数组下标对应优先级(0为最高优先级,或者反过来)。 - 也可以使用引用[1]的优先级队列(最大堆)来实现每个优先级的队列?实际上,每个优先级队列内部可以是FIFO,所以我们用链表即可。 具体实现: - 我们用一个队列数组:`TaskQueue queues[PRIORITY_LEVELS];` 其中`PRIORITY_LEVELS`是优先级级别数。 - 每个队列是一个链表,支持入队(尾插)和出队(头取)操作(因为同一优先级任务按FIFO调度,除非有抢占)。 调度算法: 1. 初始化所有队列为空。 2. 添加任务时,根据任务优先级将其加入对应优先级的队列尾部。 3. 调度: - 从最高优先级队列(假设0最高)开始检查,找到第一个非空队列,取出队头任务执行。 - 执行一个时间片(或直到任务完成,取决于是否支持时间片)。 - 如果任务完成,则释放;如果未完成且支持时间片,则将该任务放回原队列尾部(同一优先级内部轮转)或者降低优先级(多级反馈队列)后放入低一级队列的尾部。 注意:多级反馈队列需要记录任务已使用的CPU时间,并在时间片用完后降低优先级。 由于用户没有明确要求是否支持时间片和优先级调整,我们按照固定优先级多队列调度来实现,但保留扩展为多级反馈队列的可能。 代码结构: - 任务结构体 - 队列结构体(链表节点、队列头尾指针) - 全局的队列数组(每个优先级一个队列) - 初始化队列数组 - 任务入队(根据优先级) - 调度函数:选择最高优先级的非空队列,出队队首任务执行 - 执行任务(模拟执行:减少剩余时间,若完成则记录完成时间,否则重新入队) 下面我们用C语言实现一个基本的多优先级任务队列调度器(固定优先级,同一优先级FIFO)。 注意:为了简化,我们假设时间由系统时钟驱动,每次调度一个时间片(固定时间单位)。任务执行时,每次减少一个时间片的执行时间。 由于是模拟,我们不会真正执行任务,而是用减少剩余时间模拟。 具体实现: </think> ### C语言实现多优先级任务队列调度器 多优先级任务队列调度器是一种常见的任务调度机制,在操作系统中用于管理不同优先级任务。下面我将一步步说明如何用C语言实现这种调度器。 #### 1. 数据结构设计 首先定义任务控制块(TCB)和任务队列结构: ```c #define MAX_PRIORITY 5 // 优先级级别数 #define TIME_SLICE 10 // 时间片长度 // 任务状态枚举 typedef enum { READY, RUNNING, BLOCKED, TERMINATED } TaskState; // 任务控制块 typedef struct Task { int id; // 任务ID char name[20]; // 任务名称 int priority; // 任务优先级(0为最高) int burst_time; // 需要执行时间 int remain_time; // 剩余执行时间 TaskState state; // 任务状态 struct Task* next; // 链表指针 } Task; // 多优先级队列 typedef struct { Task* head[MAX_PRIORITY]; // 各优先级队列头指针 Task* tail[MAX_PRIORITY]; // 各优先级队列尾指针 } MultiPriorityQueue; ``` #### 2. 核心功能实现 ##### 初始化队列 ```c void initQueue(MultiPriorityQueue* q) { for (int i = 0; i < MAX_PRIORITY; i++) { q->head[i] = q->tail[i] = NULL; } } ``` ##### 任务入队 ```c int enqueue(MultiPriorityQueue* q, Task* task) { int prio = task->priority; if (prio < 0 || prio >= MAX_PRIORITY) return 0; task->state = READY; task->next = NULL; if (q->tail[prio] == NULL) { q->head[prio] = q->tail[prio] = task; } else { q->tail[prio]->next = task; q->tail[prio] = task; } return 1; } ``` ##### 调度器核心逻辑 ```c Task* schedule(MultiPriorityQueue* q) { // 从高优先级(0)向低优先级查找任务 for (int prio = 0; prio < MAX_PRIORITY; prio++) { if (q->head[prio] != NULL) { Task* task = q->head[prio]; q->head[prio] = task->next; if (q->head[prio] == NULL) { q->tail[prio] = NULL; } task->next = NULL; task->state = RUNNING; return task; } } return NULL; // 无就绪任务 } ``` ##### 任务执行与时间片处理 ```c void executeTask(MultiPriorityQueue* q, Task* task) { int exec_time = (task->remain_time > TIME_SLICE) ? TIME_SLICE : task->remain_time; printf("执行任务[%s] ID:%d 优先级:%d 时间:%dms\n", task->name, task->id, task->priority, exec_time); task->remain_time -= exec_time; if (task->remain_time > 0) { // 重新入队等待下一次调度 enqueue(q, task); } else { task->state = TERMINATED; free(task); } } ``` #### 3. 主调度循环 ```c void runScheduler(MultiPriorityQueue* q) { while (1) { Task* task = schedule(q); if (task == NULL) { printf("所有任务执行完成\n"); break; } executeTask(q, task); } } ``` #### 4. 使用示例 ```c int main() { MultiPriorityQueue scheduler; initQueue(&scheduler); // 创建示例任务 Task task1 = {1, "TaskA", 0, 25, 25}; Task task2 = {2, "TaskB", 2, 40, 40}; Task task3 = {3, "TaskC", 1, 30, 30}; // 任务入队 enqueue(&scheduler, &task1); enqueue(&scheduler, &task2); enqueue(&scheduler, &task3); // 启动调度器 runScheduler(&scheduler); return 0; } ``` #### 关键技术点 1. **优先级管理**:使用多队列结构,每个优先级对应一个独立队列 2. **调度策略**:固定优先级抢占式调度,高优先级队列优先服务 3. **时间片轮转**:每个任务执行固定时间片后被抢占 4. **状态管理**:通过状态机跟踪任务生命周期 5. **队列操作**:使用链表实现高效的任务插入和提取 这个实现满足以下需求: - 支持多优先级(0为最高) - 高优先级任务优先执行 - 同优先级任务采用FIFO调度 - 时间片轮转保证公平性 - 任务完成后自动释放资源
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值