草庐IT

从零开始构建实时操作系统—任务切换

程序猿李巍 2023-03-28 原文

1、前言

随着计算机技术和微电子技术的迅速发展,嵌入式系统应用领域越来越广泛,尤其是其具备低功耗技术的特点得到人们的重视。随着工信部提出NB-IoT基站建设具体目标、三大运营商加速建设,即将迎来万物互联的新时代,这是信息产业继移动互联网之后的下一个万亿级市场,这些为实时操作系统的应用提供了广阔的前景。

嵌入式实时操作系统将会部署到越来越多的设备中,这就要求工程师深入地了解嵌入式实时操作系统。本系列文章将和大家一起从零开始构建一个嵌入式实时操作系统,我将用最简单直白的方式一步一步搭建,我将用一篇文章的方式来总结搭建中的每个节点阶段,并开源软件工程和源代码。

2、嵌入式实时操作系统

嵌入式实时操作系统是一个特殊的程序,是一个支持多任务的运行环境。嵌入式实时操作系统最大的特点就是“实时性”,如果有一个任务需要执行,实时操作系统会立即执行该任务,不会有较长的延时。典型的实时操作系统有uCOS ,RT-Thread,FreeRTOS ,VxWorks,WinCE等。

嵌入式实时操作系统是一个特殊的程序(通常称为内核),它可以创建和控制所有任务。嵌入式实时操作系统除了包含一个内核以外,还提供其他服务,如文件系统,协议栈,图形用户界面等。本文的重点在于了解嵌入式实时操作系统内核的工作原理和结构,因此文中提到的实时操作系统通常指的是操作系统内核。实时操作系统内核通常要占用5%左右的CPU运行时间,另外内核是一个软件代码,需要额外占用ROM空间和RAM空间。

嵌入式实时操作系主要由以下3个子系统组成:

  • 任务调度子系统
  • 任务通信子系统
  • 内存管理子系统

3、实现目标

本文讲解构建嵌入式实时操作系统的第一个节点阶段:实现简单的任务切换功能。

代码区的数据是不变的,处理器寄存器的值和栈空间的值决定程序运行状态。让每个任务“独享”一个栈空间,当我们将任务运行时的处理器寄存器的值保存起来时,这样就实现保存任务的运行状态。同样的当我们把保存的任务运行时的处理器寄存器的值装载到处理的寄存器中时,这样就恢复了任务的运行状态,任务继续运行起来。

切换任务的原理是:每个任务有一个“独享”栈空间,通过保存和装载任务运行时的处理器寄存器的值,实现任务的暂停和恢复运行。暂停一个任务后再恢复另外一个任务就完成了一次任务切换。

任务代码,任务栈空间和处理器状态如下图:

4、实验环境

硬件是基于意法半导体的STM32F401(ARM公司的Cortex-M4内核),软件开发使用的是KEIL V5.2 开发工具。

软件工程如下:

软件工程中包含:main.c ,startup_stm32f401xc.s 和 readme三个文件。startup_stm32f401xc.s文件为STM32F401的启动文件,main.c文件实现任务切换功能,readme文件用于记录版本修改日志。

5、代码实现

切换任务的原理是让每个任务都有一个“独享”栈空间,通过保存和装载任务运行时的处理器寄存器的值,实现任务的暂停和恢复运行。暂停一个任务后再恢复另外一个任务就完成了一次任务切换。

因此需要实现:

  • 每个任务的独立栈空间。
  • 实现任务的暂停和恢复。
  • 实现任务的调度。

(1)实现独立栈空间

栈空间代码如下:

为每个任务定义一个静态数组,当任务运行时将处理器的栈指针指向任务“自己的”静态数组,从而实现独立栈空间。栈空间用来存放局部变量,中断调用和函数调用时的处理器寄存器的值。任务切换时需要将处理器寄存器的值保存到任务的独立栈空间。

在保存任务运行状态时需要保存处理器寄存器值到栈空间,因此需要深入了解处理器寄存器的用途和出入栈顺序,Cortex-M4内核的寄存器和寄存器中断自动入栈的顺序图如下:

初始化栈空间的代码如下:

栈空间初始化后的状态如下:

栈是一中先入后出的数据结构,Cortex-M4内核的栈操作方式倍设置成了向下生长。psp_array用于保存任务栈指针,psp_array[0]任务0栈指针指向task0_stack[112],其中task0_stack[116]保存PC程序指针值,task0_stack[117]保存状态寄存器(符合Cortex-M4内核寄存器出栈顺序:手动出栈8个寄存器,硬件自动出栈8个寄存器)。

(2)实现任务的暂停和恢复

代码如下:

cortex-M4内核有一个PendSV(可挂起的系统调用)异常,其异常编号为14并且具有可编程的优先级。当软件将PendSV设置成挂起时,程序将进入PendSV异常(中断)。

将PendSV异常优先级设置为最低,其它中断函数都可以得到正常响应,不会受到PendSV异常影响,在PendSV异常中执行任务切换,时序框图如下:

PendSV_Handler为Cortex-M4内核中断服务函数,进入中断函数时处理器自动保存了R0,R1,R2,R3, R12,LR,PC,XPSR,在PendSV_Handler中断程序中完成R4~R11入栈保存工作,从而实现任务保存工作。

/* 读取当前进程栈指针数值 */
MRS R0,PSP
/* 保存R4-R11八个寄存器的值到当前任务栈中 同时将回写的地址写入R0 */
STMDB R0!,{R4-R11}
psp_array[0]为任务0的栈指针, psp_array[1]为任务1的栈指针。以下代码实现任务栈指针切换。

/* 读取psp_array 地址 */
LDR R3, =__cpp(&psp_array)
/* 将当前进程PSP指针值 写入 相应的 PSP_array 位置 */
STR R0,[R3,R2,LSL #2]
/* 获取下个进程序号 */
LDR R4,=__cpp(&next_task)
LDR R4,[R4]
/* R1为&curr_task 将下个进程序号写入curr_task中 */
STR R4,[R1]
/* psp_array读取更新后的curr_task的PSP指针数值 */
LDR R0,[R3,R4,LSL #2]
在PendSV_Handler中断程序中完成R4~R11寄存器出栈,PendSV_Handler中断程序返回时处理器自动出栈R0,R1,R2,R3, R12,LR,PC,XPSR,从而实现任务恢复工作。

/* 出栈 R4-R11八个寄存器 */
LDMIA R0!,{R4-R11}
/* 设置PSP指针 */
MSR PSP,R0
/* 中断返回 */
BX LR

(3)实现任务的调度

任务调度的代码如下:

SysTick_Handler为定时器中断程序,实现时间片轮流改变目标任务,并挂起PendSV_Handle中断,退出SysTick_Handler中断程序时进入PendSV_Handle中断程序。

6、运行结果

代码仿真运行如下:

运行代码后task_num0和task_num1这两个变量依次自加,代码实现任务轮流切换功能。

有关从零开始构建实时操作系统—任务切换的更多相关文章

  1. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  2. ruby-on-rails - Ruby on Rails with Haml - 如何从 erb 切换 - 2

    我正在从erb文件切换到HAML。我将hamlgem添加到我的系统中。我创建了app/views/layouts/application.html.haml文件。我应该只删除application.html.erb文件吗?此外,仍然有/public/index.html文件被呈现为默认页面。我想创建自己的默认index.html.haml页面。我应该把它放在哪里以及如何使系统呈现该文件而不是默认索引文件?谢谢! 最佳答案 是的,您可以删除任何已转换为HAML的View的ERB版本。至于你的另一个问题,删除public/index/h

  3. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

  4. ruby - 在 Ruby 中构建长字符串的简洁方法 - 2

    在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo

  5. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  6. ruby-on-rails - Rake 任务仅调用一次时执行两次 - 2

    我写了一个非常简单的rake任务来尝试找到这个问题的根源。namespace:foodotaskbar::environmentdoputs'RUNNING'endend当在控制台中执行rakefoo:bar时,输出为:RUNNINGRUNNING当我执行任何rake任务时会发生这种情况。有没有人遇到过这样的事情?编辑上面的rake任务就是写在那个.rake文件中的所有内容。这是当前正在使用的Rakefile。requireFile.expand_path('../config/application',__FILE__)OurApp::Application.load_tasks这里

  7. ruby - 使用 rbenv 和 ruby​​-build 构建 ruby​​ 失败,出现 undefined symbol : SSLv2_method - 2

    我正在尝试在配备ARMv7处理器的SynologyDS215j上安装ruby​​2.2.4或2.3.0。我用了optware-ng安装gcc、make、openssl、openssl-dev和zlib。我根据README中的说明安装了rbenv(版本1.0.0-19-g29b4da7)和ruby​​-build插件。.这些是随optware-ng安装的软件包及其版本binutils-2.25.1-1gcc-5.3.0-6gconv-modules-2.21-3glibc-opt-2.21-4libc-dev-2.21-1libgmp-6.0.0a-1libmpc-1.0.2-1libm

  8. ruby - 帮助使用 Ruby 中的 "Whenever"gem 来执行 cron 任务 - 2

    我以前没有使用过cron,所以我不能确定我这样做是对的。我想要自动化的任务似乎没有运行。我在终端中执行了这些步骤:sudogeminstall每当切换到应用程序目录无论何时。(这创建了文件schedule.rb)我将此代码添加到schedule.rb:every10.minutesdorunner"User.vote",environment=>"development"endevery:hourdorunner"Digest.rss",:environment=>"development"end我将此代码添加到deploy.rb:after"deploy:symlink","depl

  9. ruby - 在 rake 任务中运行 capybara - 2

    如何在Rake任务中运行Capybara功能?例如:访问('http://google.com')谢谢! 最佳答案 在任务中尝试这样的事情:require'capybara'require'capybara/dsl'Capybara.current_driver=:seleniumBrowser=Class.new{includeCapybara::DSL}page=Browser.new.pagepage.visit("http://www.google.com")puts(page.html)

  10. ruby-on-rails - Ruby/Rails 中的夏令时开始和结束日期 - 2

    我正在开发一个Rails应用程序,我需要在其中找到给定特定偏移量或时区的夏令时开始和结束日期。我基本上在我的数据库中保存了从用户浏览器接收到的时区偏移量(“+3”,“-5”),我想在它出现时修改它由于夏令时的变化。我知道Time实例变量有dst?和isdst方法,如果存储在它们中的日期在夏令时与否。>Time.new.isdst=>true但是使用它来查找夏令时的开始和结束日期会占用太多资源,而且我还必须为我拥有的每个时区偏移量执行此操作。我想知道更好的方法。 最佳答案 好的,基于你所说的和@dhouty'sanswer:您希望能够

随机推荐