以嵌入式工程师的角度来实现一个简单的操作系统!
1. 前言
上一节我们已经实现了非常简单的操作系统任务创建、任务调度、任务切换等。你看出来了吗?
我们在简单的回顾一下这些功能是怎么对应的?
任务创建:
/* 创建一个任务 */
task_init(&taska, taska_stack + sizeof(taska_stack), taska_fun);
任务调度与任务切换
/* 在定时器中断中,进行任务切换
* 现在的调度策略:一共2个任务,轮流执行,每次在定时中断中切换到另一个任务
* 非常
*/
void timer_handler(struct irq_desc *desc)
{
arch_timer_compare(arch_timer_frequecy());
log_i("arch timer_handler!");
if((count++) % 2 == 0)
{
log_i("switch to taskb!");
interrupt_task_switch_from_to(&taska, &taskb);
}
else
{
log_i("switch to taska!");
interrupt_task_switch_from_to(&taskb, &taska);
}
}
上面的任务切换逻辑,非常简单,但是效率也是非常的低!
2. 优化任务调度的设计
2.1 主要问题
我们上面的程序存在几个缺点:
- 任务调度切换时,是人为规定的策略,当添加新的任务之后,还需要修改切换策略
- 任务信息结构体是分散定义的,在进行切换时需要统一管理和查找,需要优化任务结构
- 任务调度的策略不能是简单的平均执行,而是应该根据优先级、时间片的用户需求,做出一定的选择
2.2 优化方向与目标
目前我们优先实现前面2个存在的问题,实现以下2个目标:
- 创建多个任务时、无需修改任务调度的代码
- 创建多个任务时,可以让每个任务都被执行
2.3 实现方向
我们需要确保在进行任务切换的时候,找到所有的任务、遍历所有的任务
我们将定义一个全局变量数组,数组的元素就是任务结构体。我们创建任务的时候填充数组元素,在进行任务切换时,遍历数组,查找任务
在遍历任务数组的时候,我们需要通过一些方式来当前的元素是否是有效的任务,毕竟我们不一定会每一个任务数组的元素都定义了任务。假设我们定义任务数组的个数是64个,时间创建任务是4个,此时就存在60个任务数组是没有任务的。我们只需要在4个有效任务中进行切换即可。
如何区分任务是否是有效的任务,可以通过多种方式,比如在任务结构体中添加一个成员叫做pid任务编号。当任务无效的时候设置pid=-1
,当创建任务时,从全局的任务数组中分配一个任务结构体,然后分配一个唯一的大于等于0的pid号。作为有效任务的标记。
3. 代码编写
- 为任务信息结构体添加一个pid成员变量
struct task
{
void *sp; // sp
unsigned long spsr; //程序状态寄存器
unsigned long elr; //异常返回寄存器
long pid; //任务id,非负数时表示有效任务
};
- 定义全局变量,任务信息结构体数组
#define G_TASK_NUMBER 64
struct task g_task[G_TASK_NUMBER];
- 初始化全局任务信息
/**
* @brief 初始化全部任务为无效任务
*
* @return int 支持的任务数量
*/
int global_task_config(void)
{
int i = 0;
for(i; i < G_TASK_NUMBER; i++)
{
g_task[i].elr = 0;
g_task[i].sp = 0;
g_task[i].spsr = 0;
g_task[i].pid = -1;
}
return i;
}
- 修改创建任务实现,需要从全局变量获取任务
//1. 定义一个获取空闲任务和释放空闲任务得到函数
//2. 创建任务时候需要获取任务,删除任务的时候需要释放任务
/**
* @brief 请求获取一个空闲任务结构体
*
* @return struct task* 申请到的任务
*/
struct task *requset_task()
{
for(int i = 0; i < G_TASK_NUMBER; i++)
{
if(g_task[i].pid > 0)
{
//只要保证各个任务的pid非负并且不重复就可以
g_task[i].pid = i;
return &g_task[i];
}
}
return NULL;
}
/**
* @brief 将指定的任务释放掉,变为空闲状态
*
* @param free
* @return long
*/
long free_task(struct task *free)
{
long pid = free->pid;
free->pid = -1;
return pid;
}
//3. 从全局任务结构体去创建任务
/**
* @brief 从全局任务信息中创建一个任务,返回任务的pid
*
* @param sp_addr
* @param pc_addr
* @return long
*/
long task_create(void *sp_addr, void *pc_addr)
{
struct task *new_task;
new_task = requset_task();
if(new_task != NULL)
{
task_init(new_task, sp_addr, pc_addr);
return new_task->pid;
}
else
{
printf("error: can not get task!\n");
return -1;
}
}
- 实现自动调度 功能
今天有点晚了,还没有想好怎么实现。明天继续研究一下
欢迎大家持续关注,作为一个小白,我会一步步去实现一些操作系统的基本功能,逐渐丰富
在学习过程中,也会参考一些现成的RTOS或者操作系统的相关实现,比如rtthread等,可以提供一些灵感
在学习中遇到问题欢迎私信一起交流
代码在github同步开源开发,如果无法浏览github代码,可以关注后在后台联系,提供获取方式
附项目链接:https://github.com/jhbdream/armv8_os