手写一个操作系统

前言

目前成熟的操作系统有很多,但随着版本的不断更新,以及日益增多的功能需求,操作系统的“体积”逐渐增大,对于我这种只想了解其原理的人来说很不友好(尤其是那一堆宏,你都不知道有没有用,也不清楚在哪预定义了)。于是便跟着各路大神,结合实际情况,糅合了一个简化版的“操作系统”,时隔较久,已忘记都参考哪了,在此只是记录。

原理

        所有的C语言代码,最后转化为二进制的指令码,实际上都是操作寄存器和内存在一定条件下交换指定的数据。在操作系统中,每个任务拥有自己的内存(栈),但寄存器只有一份(所有任务共享一套硬件寄存器),因此操作系统的实质为:引导任务有序的分时复用寄存器。

这里的分时复用,和中断服务不能说及其相似,只能说“一模一样”。

        *中断的执行流为

                任务执行--->中断请求--->保护现场--->响应中断--->恢复现场--->任务执行。

        *任务的切换流为:

        任务1执行--->切换请求--->保护任务1栈--->查找任务--->恢复任务2栈--->执行任务2。

这不就中断的拓展版嘛,接下来我们实现这个流程。

千里之行,始于足下。流程有了,接着我们把环境搭起来,这里先介绍寄存器。

CM3的寄存器组如图所示,我将寄存器分为三组

红色:需要手动保存。

蓝色:中断发送时硬件自动入栈。

绿色:中断发送时硬件自动入栈(SP不用入栈:SP自身即为栈指针)。

自动入栈顺序如下:

1.任务栈保存与恢复

有了寄存器基础,结合前言描述的流程,就可以实现任务栈的保存与恢复了。

具体流程为:

     1)在任务切换函数中当前栈指针(此时硬件应该已经自动入栈了当前任务的R0-R3,R2,RL,PC,XPSR)。

        2)手动入栈当前任务的R4-R11,调试更新PSP。

        3)查找并获取目标任务栈。

        4)手动出栈R4-R11,触发中断返回。

        5)硬件根据栈指针自动恢复目标的R0-R4,R12,RL,PC,执行目标任务。

__asm void PendSV_Handler(void)
{ 
    extern threadSwitch;
    // 保存当前任务的寄存器内容
    
    MRS    R0, PSP     // 得到PSP  R0 = PSP
                       // xPSR, PC, LR, R12, R0-R3已自动保存
    STMDB  R0!,{R4-R11}// 保存R4-R11共8个寄存器得到当前任务堆栈
    bl     threadSwitch //选择下一个任务
    LDMIA  R0!,  {R4-R11}// 将任务堆栈中的数值加载到R4-R11中 
    MSR    PSP, R0     // 设置PSP指向此任务
		LDR    LR, =0xFFFFFFFD   // 表明中断返回 设置返回线程模式,使用PSP
    BX     LR          // 返回 xPSR, PC, LR, R12, R0-R3会自动的恢复         
    ALIGN  4
}

任务切换通过PendSV_Handler实现,即手动触发一个切换请求,本系统未完善信号量以及其他IPC,目前只实现定时切换。

/**
 * @brief 配置pendsvc优先级
 * 
 * @return 
 */
__asm void SetPendSVPro(void)
{
NVIC_SYSPRI14   EQU     0xE000ED22
NVIC_PENDSV_PRI EQU           0xFF
    
    LDR     R1, =NVIC_PENDSV_PRI    /** 将值 NVIC_PENDSV_PRI 加载到R1 */
    LDR     R0, =NVIC_SYSPRI14      /** 将值 NVIC_SYSPRI14 加载到R0 */
    STRB    R1, [R0]                /** 将 NVIC_PENDSV_PRI保存到NVIC_SYSPRI14 */
    BX      LR                      /** 中断返回 */
		nop
}
	
/**
 * @brief 触发pendsvc中断
 * 
 * @return
 */
__asm void TriggerPendSV(void)
{
NVIC_INT_CTRL   EQU     0xE000ED04                              
NVIC_PENDSVSET  EQU     0x10000000                              

    LDR     R0, =NVIC_INT_CTRL    /** 将常量 0xE000ED04 加载到 R0*/                             
    LDR     R1, =NVIC_PENDSVSET   /** 将常量 0x10000000 加载到 R1 */
    STR     R1, [R0]              /** 将 0x10000000保存到0xE000ED04 */
    BX      LR                    /** 中断返回 */
		nop
}

        2.任务选择:

        当有任务请求切换时,通过一定的算法,查找下一个任务,本文实现基于系统心跳的可剥夺调度算法。

static void findThread(void)
{
  uint8_t index = 0U;
  
  uint32_t tick = sysTick;
	targTask = crtTask;
  if (THREAD_STA_READY == tcpSta[crtTask].threadSta) /** 当前任务就绪 */
  {
    for ( index = 0u; index < crtTask; index++)     /** 查找更高优先级任务,数值越小,优先级越高 */
    {
      if (tcpSta[index].blockSta)                   /** 如果该优先级激活 */
      {
        if (THREAD_STA_READY == tcpSta[index].threadSta)  /** 如果任务就绪,切换到该任务 */
        {
           targTask = index;
           break;
        }
        else
        {
          if (tick >= tcpSta[index].tickOut) /** 如果任务未就绪,查看是否超时,超时也就绪 */
          {
            tcpSta[index].threadSta = THREAD_STA_READY;
            targTask = index;
            break;
          }
        }
      }
    }
  }
  else  /** 当前任务未就绪 */
  {
    if (THREAD_STA_PENDDING == tcpSta[crtTask].threadSta) /** 任务因超时阻塞 */
    {
      if (tick >= tcpSta[crtTask].tickOut)  /** 确认超时则更新为就绪 */
      {
        targTask = crtTask;
        tcpSta[crtTask].threadSta = THREAD_STA_READY;
      }
    }
    else /** 如果是主动让出,更新为就绪 */
    {
      //tcpSta[crtTask].threadSta = THREAD_STA_READY;
    }
    
    for ( index = crtTask+1U; index < TASK_MAX; index++)/** 查找最高优先级 */
    {
      if (tcpSta[index].blockSta) /**  如果当前优先级有任务注册 */
      {
        if (THREAD_STA_READY == tcpSta[index].threadSta) /** 检查是否就绪 */
        {
          targTask = index;
          break;
        }
        else
        {
          if (tick >= tcpSta[index].tickOut) /** 超时则任务就绪 */
          {
            targTask = index;
            tcpSta[targTask].threadSta = THREAD_STA_READY;
            break;
          }
        }
      }
    }
  }
  
  if (targTask != crtTask) /** 如果需要切换任务 */
  {
    crtTask = targTask; /** 更新任务 */
    crtTcp = &tcpSta[crtTask]; /** 更新控制块 */
  }
  else
  {
    /** 没有更高优先级任务,且本任务未就绪,执行空闲任务 */
    if ((THREAD_STA_PENDDING == tcpSta[crtTask].threadSta) || (THREAD_STA_YIDLE == tcpSta[crtTask].threadSta))
    {
      crtTask = TASK_MAX-1U;
      crtTcp = &tcpSta[crtTask];
    } 
  }
}


/**
 * @brief 任务切换
 * 
 * @param psp 当前任务栈
 * @return uint32_t* 目标任务栈
 */
uint32_t* threadSwitch(uint32_t* psp)
{
  static uint8_t firstSwitch = 1U;
  uint32_t *ret = psp;

  if (firstSwitch) /** 第一次运行任务,默认为空闲任务 */
  {
    crtTcp = &tcpSta[IDLE_TASK_PRIORITY];
    firstSwitch = 0;
  }
  else /** 记录当前任务栈 */
  {
    crtTcp->stackPtr = psp;
    findThread(); /** 查找最高优先级任务 */
  }

  ret = crtTcp->stackPtr; /** 获取任务栈地址 */

  return ret;
}

3.应用

创建几个不同优先级的任务,以系统定时器为心跳,定时检查任务优先级并执行切换。

  threadCreate(LED1_TASK_PRIORITY,task0);
  threadCreate(LED2_TASK_PRIORITY,task1);
  threadCreate(OLED_TASK_PRIORITY,OledTask);
  threadCreate(UART_TASK_PRIORITY,Uart2Task);
  threadStart();

总结

本文有点仓促,很多细节没有体现,一个原因是时间隔得久,有点忘了,主要原因是现在各种RTOS都很成熟,我也没有将这个系统继续完善的计划,就当是备忘录吧。

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值