目录

  • 一、书接上回
  • 二、初始化过程
  • 三、任务的创建
  • 四、任务的切换
  • 五、任务的等待(系统延时)

一、书接上回

上回写了一个测试程序,可以直观的体会PC指针和堆栈指针的变化和影响。这章写下参考程序的过程原理。
源码我已上传,免积分,贴在第一章末尾

上回链接:
【深入学习51单片机】一、基于8051的RTOS内核任务切换堆栈过程剖析

二、初始化过程

main函数:

int main(void)
{system_init();os_init();os_task_create(1,task_1,(unsigned char)os_tsak_stack[1]);os_task_create(0,task_0,(unsigned char)os_tsak_stack[0]);os_start();return 0;
}
  1. system_init()

    • 是空的,用来初始化用户配置
  2. os_init()
    • 初始化了用来切换任务堆栈的定时器,上章提到了用定时器切换任务,就是指利用中断函数调用过程中PC指针自动入栈,修改栈地址后PC指针弹出到目标地址。
    • 初始化了系统空闲任务,因为系统在空闲的时候总要有段程序跑,就是这个
    • 源码:
 /* 初始化系统 */
void os_init(void)
{EA = 0;ET0 = 1;          //定时器0开中断AUXR &= 0x7f;     //12T模式TMOD &= 0xf0;TMOD |= 0x01;     //16位计数器TH0 = 0xee;TL0 = 0x00;            //5ms中断os_int_count = 0;        //嵌套层数初始化os_task_rdy_tab = 0;         //任务就绪表初始化os_task_create(MAX_TASK-1,task_idle,(unsigned char)os_tsak_stack[MAX_TASK-1]); //系统idle任务初始化
}
  1. os_task_create(1,task_1,(unsigned char)os_tsak_stack[1]);

    • 初始化用户任务task_1
    • 指定优先级为1
    • 指定任务堆栈
  2. os_task_create(0,task_0,(unsigned char)os_tsak_stack[0]);
    • 初始化用户任务task_0
    • 指定优先级为0
    • 指定任务堆栈
  3. os_start();
    • 查找优先级最高的就绪态任务
    • 切换任务堆栈到优先级最高的就绪态任务,也就是调度器
    • 开始运行内核
    • 源码:
/* 任务开始运行 */
void os_start(void)
{unsigned char i;for(i=0; i<MAX_TASK; i++){if(os_task_rdy_tab & os_map_tab[i]) //查找任务就续表{break;}}os_task_running_id = i;     //优先级最高的先运行EA = 1;SP = os_tcb[os_task_running_id].os_task_stcak_bottom + 1;  //弹出是任务地址TR0 = 1;os_running = os_true;
}

三、任务的创建

源码:

/* 创建任务 */
void os_task_create(unsigned char task_id,void (*task)(void),unsigned char stack_point)
{char cpu_sr;os_enter_critical();((unsigned char idata*)stack_point)[0] = (unsigned int)task;((unsigned char idata*)stack_point)[1] = (unsigned int)task >> 8;  //任务地址放在栈底两个字节os_tcb[task_id].os_task_stcak_bottom = stack_point;                  //栈底
//  os_tcb[task_id].os_task_stcak_top = stack_point + 14;             //栈顶(起始这里应该,对任务堆栈初始化后的指针)//为什么要加1?(因为栈中已经有数据了task)当然初不初始化都可以,用上面的也行(只不过好多人对为什么14有疑惑)os_tcb[task_id].os_task_stcak_top = os_tesk_stack_init(stack_point + 1);  os_task_rdy_tab |= os_map_tab[task_id];                                //更新任务就绪表os_tcb[task_id].os_tsak_wait_tick = 0;                                //无等待os_tcb[task_id].suspend = 0;                                      //任务以就绪os_exit_critical();
}

任务创建放在临界段(就是失能中断和使能恢复中断的操作之间的代码)处理,中断状态在临时变量cpu_sr中保存,有几个重要的数据结构:

  • 任务控制表,记录堆栈操作关键位置
/*任务控制表 */
typedef struct os_task_control_table
{unsigned char os_tsak_wait_tick;unsigned char os_task_stcak_top;unsigned char os_task_stcak_bottom;unsigned char suspend;
}TCB;
  • 优先级映射表,任务优先级调度用的就是这个表
const unsigned char os_map_tab[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

如果理解了上一章讲的东西,那这里就较好理解了。

  • 先把用户定义的任务地址压到栈底,然后初始化栈底和栈顶
  • 任务优先级和os_map_tab会绑定,然后更新就绪态到任务就续表os_task_rdy_tab
  • 最后进行任务状态设置(等待和挂起)

四、任务的切换

源码:

/* 任务切换心跳 */
void timer0_isr(void) interrupt 1   //using 0 默认用,所有的寄存器会自动入栈,
{                           //  用using 1 2 3 则需要手动对r0-r7入栈,出栈(请查看寄存器组选择(看任意一本讲51的书))unsigned char i;char cpu_sr;if(os_true == os_running){os_enter_critical();/*  寄存器入栈(注释的部分是在进入中断函数前已经压入栈中(函数调用会让PC压栈,中断函数不但会让PC压栈,还会对下面五个寄存器压栈),就是我写的这个顺序) *///    __asm PUSH ACC          //  __asm PUSH B//  __asm PUSH DPH//    __asm PUSH DPL//    __asm PUSH PSW
//      __asm PUSH 0
//      __asm PUSH 1
//      __asm PUSH 2
//      __asm PUSH 3
//      __asm PUSH 4
//      __asm PUSH 5
//      __asm PUSH 6
//      __asm PUSH 7os_int_enter();os_time_tick();if(1 == os_int_count)       //当然,51单片机最多只能有一次中断嵌套,os_int_count最大为2(+本次){os_tcb[os_task_running_id].os_task_stcak_top = SP;    for(i=0; i<MAX_TASK; i++)     //找到已经就绪,未被挂起,且优先级最高的任务(任务调度器){if(os_task_rdy_tab & os_map_tab[i]){if(0 == os_tcb[i].suspend){break;}}}if(os_task_running_id != i)       //现在执行的任务就是优先级最高的,所以不需要任务切换{os_task_running_id = i;            //可执行的最高优先级任务SP = os_tcb[os_task_running_id].os_task_stcak_top;    //最高优先级任务的栈顶}}TF0 = 0;         //清除中断标志TH0 = 0xee;            //时间重装载TL0 = 0x00;os_int_exit();//     __asm POP 7
//      __asm POP 6
//      __asm POP 5
//      __asm POP 4
//      __asm POP 3
//      __asm POP 2
//      __asm POP 1
//      __asm POP 0os_exit_critical();/*和前面的道理相同(既然压栈了,当然也要出栈)(说明,后面手动加的__asm RETI,则下面的这几跳POP是必须要的,这是为什么呢?    哈哈,因为默认情况下,RETI指令是要在,POP的后面,不然就不能执行POP,这几个寄存器值就恢复不了,最重要的是SP也就乱套了 ,,,,,,,)*/// __asm POP PSW           //  __asm POP DPL// __asm POP DPH// __asm POP B//   __asm POP ACC/*写不写都一样(写了,这条语句执行完会进行上面寄存器和PC出栈,不写的话,C语言写的中断函数,编译器汇编过后,会在后面加一条reti指令,和上面执行相同的功能)【写到这里,同学我突然有这么一个想法,能不能用ret(reti对中断标志位有清零作用,这里我们手动清除标志位就可以了),函数返回指令来返回中断函数,当然这些POP之类的指令就需要收到写了,程序是玩出来的,大家可以尽情的尝试哈^_^】*///  __asm RETI  //  __asm   ret   //好像不可以,为什么呢?}}
  • os操作同样放在了临界段处理
  • 更新中断层数,因为只有这个中断,所以os_int_count只会是1
  • 更新系统心跳,用于系统延时
  • 找到已经就绪,未被挂起,且优先级最高的任务(任务调度器)
for(i=0; i<MAX_TASK; i++)      //找到已经就绪,未被挂起,且优先级最高的任务(任务调度器)
{if(os_task_rdy_tab & os_map_tab[i]){if(0 == os_tcb[i].suspend){break;}}
}
  • 运行态任务id更新,并切换堆栈指针到其栈顶,恢复现场后,SP会弹出到栈底,也就是要返回的PC指针位置

五、任务的等待(系统延时)

源码:

/* 系统延时 */
void os_delay(unsigned char tisks)
{unsigned char i;
//  char cpu_sr;if(tisks > 0){   //os_enter_critical();      EA = 0;                        //直接操作,而不用临界段的方法主要是为了任务切换更快__asm PUSH ACC               //寄存器入栈(在此之前不能有任何运算操作,不然会改变寄存器值)__asm PUSH B__asm PUSH DPH__asm PUSH DPL__asm PUSH PSW__asm PUSH 0__asm PUSH 1__asm PUSH 2__asm PUSH 3__asm PUSH 4__asm PUSH 5__asm PUSH 6__asm PUSH 7os_tcb[os_task_running_id].os_task_stcak_top = SP;os_tcb[os_task_running_id].os_tsak_wait_tick = tisks; //延时时间os_tcb[os_task_running_id].suspend = 1;                  //因为有延时,所以先挂起本任务for(i=0; i<MAX_TASK; i++)                             //找到已经就绪,未被挂起,且优先级最高的任务{if(os_task_rdy_tab & os_map_tab[i]){if(0 == os_tcb[i].suspend){break;}}}os_task_running_id = i;                              //可执行的最高优先级任务SP = os_tcb[os_task_running_id].os_task_stcak_top;    //最高优先级任务的栈顶__asm POP 7__asm POP 6__asm POP 5__asm POP 4__asm POP 3__asm POP 2__asm POP 1__asm POP 0__asm POP PSW__asm POP DPL__asm POP DPH__asm POP B__asm POP ACCEA = 1;//os_exit_critical();//__asm RET //后面是函数返回,即 ret//__asm reti}
}
  • 压栈,保存现场,更新栈顶
  • 保存延时时间,然后挂起任务
  • 找到已经就绪,未被挂起,且优先级最高的任务
  • 切换正在运行的任务id:os_task_running_id
  • 堆栈指向该id的任务
  • 现场恢复,SP会弹出到栈底,也就是要返回的PC指针位置

#五、小结
因为有现场保存和恢复,看起来复杂了很多,最好是仿真单步跟着走,否则SP还是很绕的。。。

【深入学习51单片机】二、一个极简RTOS源码分析相关推荐

  1. 基于51单片机的智能门禁控制系统(仿真+源码+全套资料)

    资料编号:119  功能讲解: 采用51单片机作为CPU控制,继电器驱动门锁的打开与关闭,采用按键模拟指纹开锁,以及内部开锁相关信号,当指纹正确门锁可以正常打开,指示灯亮绿灯,如果指纹错误,门锁无法打 ...

  2. activiti学习(二十一)——流程虚拟机源码分析(三)——从进入到离开userTask

    前言 承接上文<activiti学习(二十)--流程虚拟机源码分析(二)--从开始节点离开到下个节点前>,假设execution接下来进入的节点是userTask,本文分析一下进入user ...

  3. e admin admin.php,EAdmin极简社区源码

    EAdmin极简社区源码,是一套基于ThinkPHP5 开发的极简论坛程,是一套通用管理平台,后台设计本着谁都可以用来开源扩展开发的原则,融入了多项通用功能.前台可以基于插件开发出很多小功能,不再需要 ...

  4. 基于51单片机的智能窗帘项目,源码+原理图+pro仿真。

    基于51单片机的智能窗帘项目,源码+原理图+pro仿真. 介绍 :此作品有四种模式,通过四个独立按键调控,模式1:三个光感范围,使得电机驱动窗帘自动开,半开,关;模式2:15-25度的温度阈值,低于或 ...

  5. 从原理到实现丨手把手教你写一个线程池丨源码分析丨线程池内部组成及优化

    人人都能学会的线程池 手写完整版 1. 线程池的使用场景 2. 线程池的内部组成 3. 线程池优化 [项目实战]从原理到实现丨手把手教你写一个线程池丨源码分析丨线程池内部组成及优化 内容包括:C/C+ ...

  6. 【学习笔记】Keras库下的resnet源码分析

    Keras库下的resnet源码应用及解读 其实我也不知道这种东西有没有写下来的必要,但是跑代码的时候总摸鱼总归是不好的.虽然很简单,不过我也大概做个学习记录,写给小白看的.源码来自keras文档,大 ...

  7. SpringBoot-web开发(二): 页面和图标定制(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...

  8. FairFuzz 论文简读+源码分析+整体流程简述

    FairFuzz: A Targeted Mutation Strategy for Increasing Greybox Fuzz Testing Coverage 一.论文阅读 Abstract ...

  9. at89c52串口通信c语言程序,AT89C52DEMO 基于51单片机的(89C51/52)C语言源码例程 - 下载 - 搜珍网...

    例程与源码/12864带字库测试程序/12864 例程与源码/12864带字库测试程序/12864.hex 例程与源码/12864带字库测试程序/12864.lnp 例程与源码/12864带字库测试程 ...

最新文章

  1. 理解JavaScript面向对象的思路
  2. 静态代理和动态的本质区别
  3. Mycat分库路由规则
  4. 玛丽卡(codevs 1021)
  5. 关于Javascript表单验证
  6. inPixio Photo Studio 11(图片编辑软件)官方正式版V11.0.7709.20526 | 超好用的图片编辑器
  7. mysql函数返回结果集_MySQL自定义函数
  8. 银河麒麟ARM64 飞腾FT2000 linuxdeployqt linux打包qt
  9. BACKUP SET和BACKUP PIECE
  10. 基于天地图热力图及区域划分
  11. EDK2源码下载及环境搭建
  12. 豆瓣上征婚交友的小姐姐们
  13. python中 math模块下 atan 和 atan2的区别
  14. 响应式设计:理解设备像素,CSS像素和屏幕分辨率
  15. 李开复写给中国大学生的七封信(3/7)
  16. 如何将SQL语句进行自动翻译
  17. 放置江湖服务器维护,放置江湖挂机收益如何最高 挂机收益最高时间分析[图]
  18. RINEX 采用的格式说明
  19. php实现大文件断点续传
  20. js 格式化中国标准时间为YY-MM-DD形式并回显时间

热门文章

  1. Linux 中的 READ_ONCE和WRITE_ONCE
  2. Tesseract-OCR 下载安装和使用
  3. 浙江大学计算机学院合作框架,中国农科院与浙江大学“云签署”战略合作框架协议...
  4. 爬取猫眼电影《一出好戏》数据并分析
  5. oracle wip 拆解工单 操作_Oracle WIP车间作业管理.ppt
  6. 有品css进度12.6
  7. 【优化求解】基于多元宇宙MVO算法求解最优目标matlab源码
  8. python字符串分割初学
  9. 机器学习降维算法六——ISOMAP(等距特征映射)
  10. linux自动安装光盘,Linux(centos6.4)自动安装光盘制作