当前位置: 首页 > news >正文

RVOS的任务调度优化

12.系统优化–任务调度

12.1 改进任务管理功能

在原有基础上进⼀步改进任务管理功能。具体要求:改进 task_create(),提供更多的参数,具体改进后的函数如下所⽰:

int task_create(void (*task)(void* param),void *param, uint8_t priority);- param 用于在创建任务执行函数时可带入参数,如果没有参数则传入 NULL- priority 用于指定任务的优先级,目前要求最多⽀持 256 级,0 最高,依次类推。
- timeslice:任务的时间片大小,单位是操作系统的时钟节拍(tick),此参数指定该任务一次调度可以运行的最大时间长度·和 priotity 相结合,调度器会首先根据 priority 选择优先级最高的任务运行,而 timeslice 则决定了当没有更高优先级的任务时,当前正在运行的任务可以运行的最大时间长度。

这里没啥好说的,难点就是c语言基本功,各种参数的传递要搞清楚,这里用的都是指针,我们存放的也都是地址,相互之间的参数传递也是使用32为地址来进行的。

(最开始还傻乎乎的以为,void *param,我们往里面传参的时候往往是把参数打包成一个struct传入,那么我用寄存咋能放得了,比如tasks[_top].ctx.a0 = (reg_t) param;,这里其实都是地址,所以是ok的。)

#define MAX_TASKS   10
#define MAX_PRIORITY 256struct task {struct context ctx;      	// 任务上下文uint8_t priority;        	// 任务优先级uint32_t timeslice;      	// 时间片大小uint32_t remaining_ticks; 	// 剩余时间片int active;              	// 是否活跃(1: 活跃, 0: 已退出)
};uint8_t __attribute__((aligned(16))) task_stack[MAX_TASKS][STACK_SIZE];
struct task tasks[MAX_TASKS];static int _top = 0;
static int _current = -1;void sched_init()
{w_mscratch(0);w_mie(r_mie() | MIE_MSIE);
}/** DESCRIPTION*  Create a task.*  - task: 任务入口函数*  - param: 传递给任务的参数*  - priority: 任务优先级*  - timeslice: 时间片大小* RETURN VALUE*  0: success* -1: if error occurred*/
int task_create(void (*task)(void* param), void *param, uint8_t priority, uint32_t timeslice)
{if (_top < MAX_TASKS) {tasks[_top].ctx.sp = (reg_t) &task_stack[_top][STACK_SIZE];tasks[_top].ctx.pc = (reg_t) task;tasks[_top].ctx.a0 = (reg_t) param; 		// 将参数传递给任务tasks[_top].priority = priority;tasks[_top].timeslice = timeslice;tasks[_top].remaining_ticks = timeslice; 	// 初始化剩余时间片tasks[_top].active = 1; 					// 标记任务为活跃_top++;return 0;} else {return -1;}
}

12.2 优化任务调度算法

修改任务调度算法,在原先简单轮转的基础上⽀持按照优先级排序,优先选择优先级高的任务运行,同⼀级多个任务再轮转。

出现的问题:调度算法现在有明显问题,问题是高优先任务如果不结束,低优先级任务一直不会得到运行。

问题分析:

当前调度算法的问题是严格优先级调度,导致高优先级任务如果不主动退出或让出 CPU,低优先级任务永远无法运行。这种情况被称为优先级饥饿(Priority Starvation)。为了解决这个问题,可以引入时间片轮转调度动态优先级调整的机制。

改进思路

  1. 时间片轮转调度
    • 即使高优先级任务未结束,当其时间片用完后,也需要切换到其他任务(包括低优先级任务)。
    • 通过时间片管理,确保所有任务都有机会运行。
  2. 动态优先级调整
    • 随着时间推移,未运行的低优先级任务可以逐渐提升优先级,避免长期被高优先级任务压制。
void schedule(void)
{if (_top <= 0) {panic("Num of task should be greater than zero!");return;}int highest_priority = MAX_PRIORITY;int next_task = -1;// 动态优先级调整:逐渐提升未运行任务的优先级for (int i = 0; i < _top; i++) {if (tasks[i].active && i != _current) {tasks[i].priority = (tasks[i].priority > 0) ? tasks[i].priority - 1 : 0;}}// 遍历任务列表,选择优先级最高的任务for (int i = 0; i < _top; i++) {if (tasks[i].active) {if (next_task == -1 || tasks[i].priority < highest_priority || (tasks[i].priority == highest_priority && i > _current)) {highest_priority = tasks[i].priority;next_task = i;}}}if (next_task == -1) {panic("No active tasks to schedule!");return;}// 时间片管理if (_current >= 0 && _current < _top) {tasks[_current].remaining_ticks--;if (tasks[_current].remaining_ticks == 0) {tasks[_current].remaining_ticks = tasks[_current].timeslice; // 重置时间片} else if (tasks[_current].priority <= tasks[next_task].priority) {// 当前任务时间片未用完,且优先级不低于下一个任务,继续运行return;}}// 切换到下一个任务_current = next_task;struct context *next = &(tasks[_current].ctx);switch_to(next);
}

12.3 增加任务退出接口

增加任务退出接口 task_exit(),当前任务可以通过调用该接口退出执行,内核负责将该任务回收,并调度下⼀个可运行任务。建议的接⼝函数如下:

void task_exit(void);

实现思路:通过触发机器级软件中断来调用调度器,并标记当前任务为非活跃。(区分task_yield()是将任务剩余时间片清零)

/** DESCRIPTION*  task_yield() causes the calling task to relinquish the CPU and a new *  task gets to run.*/
void task_yield()
{if (_current >= 0 && _current < _top) {tasks[_current].remaining_ticks = 0; }/* trigger a machine-level software interrupt */int id = r_mhartid();*(uint32_t*)CLINT_MSIP(id) = 1;
}

出现的问题1:task_exit直接调用schedule()出现 Sync exceptions! Code = 1

问题分析:查阅RISCV体系架构的说明,该问题是指令异常。原因是直接调用 schedule() 函数时,当前任务的上下文没有正确保存,导致任务切换时访问了无效的指令地址或内存区域。

  1. 直接调用 schedule() 的问题
    • schedule() 函数会切换到下一个任务,但在切换之前,当前任务的上下文(如寄存器状态、程序计数器等)没有保存。
    • 当任务切换回来时,CPU 的状态可能已经被破坏,导致访问无效的指令地址或内存区域,从而触发 Instruction access fault 异常。
  2. 触发机器级软件中断的正确性
    • 使用 CLINT_MSIP 触发机器级软件中断时,RISC-V 的中断机制会自动保存当前任务的上下文(通过陷入中断处理程序)。
    • 在中断处理程序中,调度器可以安全地切换任务,因为上下文已经被保存。

解决方案

如果希望直接调用 schedule(),需要确保在调用之前保存当前任务的上下文。以下是两种解决方案:


方案 1:使用中断触发调度(推荐)

保持现有的实现,通过触发机器级软件中断来调用调度器。这种方式利用了硬件中断机制,确保上下文正确保存。

void task_exit(void)
{if (_current >= 0 && _current < _top) {tasks[_current].active = 0; // 标记当前任务为非活跃int id = r_mhartid();*(uint32_t*)CLINT_MSIP(id) = 1; // 触发机器级软件中断}
}

方案 2:手动保存上下文后调用 schedule()

在直接调用 schedule() 之前,手动保存当前任务的上下文。

void task_exit(void)
{if (_current >= 0 && _current < _top) {tasks[_current].active = 0; // 标记当前任务为非活跃// 手动保存上下文struct context *current_ctx = &(tasks[_current].ctx);save_context(current_ctx); // 假设有一个保存上下文的函数// 调用调度器schedule();}
}

save_context() 函数需要实现保存当前任务的寄存器状态、程序计数器等上下文信息。

总结:

  • 推荐使用中断触发调度:通过 CLINT_MSIP 触发机器级软件中断,确保上下文正确保存。
  • 避免直接调用 schedule():如果必须直接调用,需确保在调用之前手动保存上下文,但实现复杂且容易出错。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

运行显示,任务可以正常退出,正常调度。

12.4 优化任务调度数据结构,提升调度效率

当前的调度器使用了简单的数组和 for 循环来遍历任务列表,导致在任务数量较多时效率低下。为了优化调度效率,可以引入更高效的数据结构和算法来管理任务队列和优先级。

这个其实应该在最开始优化的,数据结构出问题,感觉就是地基出问题,需要大改。

http://www.xdnf.cn/news/23869.html

相关文章:

  • OJ笔试强训_1至24天
  • `peft`(Parameter-Efficient Fine-Tuning:高效微调)是什么
  • 接口测试的原则、用例与流程
  • Git学习之路(Updating)
  • 《软件设计师》复习笔记(11.3)——需求获取、分析、定义、验证、管理
  • 欧拉系统升级openssh 9.7p1
  • 【AI】实现中文文章摘要的AI模型
  • 【失败】Gnome将默认终端设置为 Kitty
  • 如何在Linux系统中部署C++ Web应用
  • Sa-Token使用指南
  • 1 Celery 简介
  • cpolar 内网穿透 实现公网可以访问本机
  • top100 (6-10)
  • 字符串循环拼接,不能用 + 连接, 需要用 StringBuilder 代替
  • 全球唯一电解方式除湿器 / 加湿器 RS1 ROSAHL 微型 易安装
  • Logisim数字逻辑实训——寄存器设计与应用
  • 稳态模型下的异步电机调速【运动控制系统】
  • 《软件设计师》复习笔记(13)——结构化开发方法
  • 2021-11-09 C++倍数11各位和为13
  • 哈电汽轮机携林重型燃机登陆2025涡轮展,5月苏州相见
  • 嵌入式通信协议与编程逻辑完全指南
  • 数据表示与运算
  • MOSI和MISO别连反了
  • Thymeleaf简介
  • zemax非序列棱镜面元理解
  • Logisim数字逻辑实训——计数器设计与应用
  • Pytest 的配置和命令行选项:掌控你的测试执行 (Pytest 系列之七)
  • AbMole推荐——肿瘤类器官加速癌症研究成果产出
  • [Python入门学习记录(小甲鱼)]第6章 函数
  • text-decoration: underline;不生效