调度器 – Blanboom https://blanboom.org Fri, 14 May 2021 11:03:52 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.5.3 https://blanboom.org/wp-content/uploads/2018/03/cropped-favicon.001-32x32.png 调度器 – Blanboom https://blanboom.org 32 32 53782978 Stanford CS140e 学习笔记 (4):用户态程序的运行 https://blanboom.org/2018/cs140e-learning-notes-4/ https://blanboom.org/2018/cs140e-learning-notes-4/#respond Mon, 18 Jun 2018 08:48:37 +0000 https://blanboom.org/?p=701 阅读全文]]> Assignment 3 的主要目标,是使自己的操作系统能够运行用户态程序。这个 Assignment 中,需要实现特权级别切换、上下文切换、调度器、系统调用处理、虚拟内存等代码。并将之前实现的 shell 移动至用户态,做为一个进程来运行。

启动第一个进程

文章目录:

ARM 架构基础知识学习

Assignment 3 所实现的不少功能,都需要操作系统软件和 CPU 的配合。所以,在正式实验之前,需要了解 ARM 架构的基础知识。主要参考如下文档:

为了实现用户态和内核态之间的切换,首先需要关注异常和特权级别。原网页已经有较为详细的介绍。同时我又找了这样几篇文章用于加深理解。

同时也进行了指令集、寻址方式的简单学习。并阅读了 init.S 初始化程序。

异常与中断处理

切换到 EL1

AArch64 CPU 启动时,EL 级别位于 EL2. 正常情况下,OS 内核工作于 EL1,用户态程序工作于 EL0。所以需要在内核初始化时,将 EL 级别由 EL2 切换到 EL1.

异常向量

为了使内核能够处理终端和异常,需要设置异常向量。ARM 的向量表中有 16 个异常向量,每条向量可存放最多 16 条指令。为了更方便地处理异常,每条异常向量,都将调用 handle_exception 函数,并设置不同的参数,最终由 handle_exception 函数统一处理。

通过汇编代码调用 handle_exception 时,需要考虑传入参数,获取返回值。对于 AArch64,前 8 个参数依次传入到 r0..r7,函数调用完毕后,前 8 个返回值也从 r0..r7 中获取。另外,在调用函数前后,还需要保存和恢复各个寄存器中的值,这些寄存器分为 caller-savedcallee-saved,对于前者,如果 caller 需要在函数执行调用后继续使用寄存器中的内容,则需要主动将寄存器中的值保存在栈上,否则不需要保存;对于后者,由 callee 保证寄存器的值在函数调用后不发生变化。

在异常处理中,实现了一个 debug shell,方便后续进行调试。

上下文切换

进入异常后,寄存器的状态可能会被异常处理程序改变,导致从异常返回后,程序无法继续执行。所以进入异常、以及从异常返回时,需要分别保存、恢复各个寄存器的状态。这一过程称为「上下文切换」。

第一个进程

对于内核,需要为每个进程维护如下信息:

  • 可执行代码
  • 虚拟地址空间
  • 调度器状态(进程是否需要被得到调度)
  • 运行状态(寄存器等信息,方便上下文切换后进行恢复)

所以在代码中,通过定义一个名为 Process 的结构体,保存进程运行所需的信息。

然后将之前实现的 shell,修改为一个用户态 (EL0) 进程。由于未实现虚拟内存,这个进程除了运行在 EL0 之外,更像是一个内核线程。对于虚拟内存的部分,留在后面的课程中实现。

调度器

定时器中断

在 AArch64 上,中断其实和异常基本类似,知识中断往往由外部事件产生。我们需要做的,是修改之前完成的定时器驱动代码,设置 BCM2837 上的定时器,同时实现中断控制器驱动,使定时器能够正常产生中断。

定时器驱动代码的修改,和之前玩单片机时使用的方式类似。通过设置一个时间比较寄存器,当定时器中的时间与该寄存器的时间相等,则产生一个中断。通过这种方式,周期性地产生中断,做为内核的时钟使用。

中断控制器也需要经过设置,才能将定时器产生的中断送至 CPU。

round-robin 抢占式调度器

之前在单片机上实现过一个简单的非抢占式的调度器,这门课程中,需要实现另外一个。

调度器通过前面实现的定时器中断,来确定接下来要运行哪个进程。用于调度的算法有很多种,本课程中,主要实现简单的 round-robin 调度算法,为每个进程分配相同的时间片。

在这个操作系统中,调度器为进程有三种状态:Ready, Running, Waiting. 所有进程放在一个队列中,当前进程的时间片用尽,则将进程放入队列末尾,并取出一个新的进程。如果新的进程状态为 Ready, 则继续执行,如果为 Waiting,则直接放入队列末尾,并再取一个新的进程执行。

系统调用

接下来要实现第一个系统调用 sleep,告诉调度器制定时间内不对本进程进行调度。同时通过在 shell 中实现 sleep 命令,验证该系统调用是否能正常工作。

系统调用是一种特殊的异常,是用户态进程请求内核服务的一种方式。在 AArch64 中,通过 svc 指令来执行系统调用。

类似函数调用,在系统调用中,也需要传入参数,获取返回值。本课程中实现的系统调用中,将寄存器 x0..x6 用做传入参数与返回值,将 x7 用做错误码。

更多

课程的剩余内容,CS140e 的网站上已经几个月没有更新了。这门课程的学习暂时告一段落,等课程更新后再继续。

扩展阅读

]]>
https://blanboom.org/2018/cs140e-learning-notes-4/feed/ 0 701
[Arduino 库] 适用于 Arduino Uno 的多任务调度程序 https://blanboom.org/2013/arduino-task-scheduler-library/ https://blanboom.org/2013/arduino-task-scheduler-library/#comments Sat, 27 Jul 2013 01:45:32 +0000 https://sandbox.blanboom.org/?p=273 阅读全文]]> 一般情况下,处理 Arduino 的多个任务,是把所有任务放在 void loop() 里,然后用 delay() 控制时间。不过,任务一多,这种方法就不太方便了。

最近刚刚看了一本书:《时间触发嵌入式系统设计模式》,里面介绍的调度器,可以以特定的周期执行特定的任务,值得在 Arduinio 项目中借鉴。我也刚刚把这个调度器移植到 Arduino 中:https://github.com/blanboom/Arduino-Task-Scheduler

基本使用方法

这是一个使用调度器的例子,各个函数的功能都已在注释中标出:

// Arduino 任务调度器 演示程序
// Created by Blanboom
// 2013.7.27
// https://blanboom.org

#include "TaskScheduler.h"  //包含此头文件,才能使用调度器

// 用于储存 LED 状态
boolean g_led1State=1;
boolean g_led2State=0;

void setup()
{
    // 第12、13脚接有 LED
    pinMode(13,OUTPUT);
    pinMode(12,OUTPUT);

    Sch.init(); //初始化调度器

    //向调度器中添加任务
    //第一个参数为要添加任务的函数名
    //第二个参数为任务第一次执行的时间,
    //    合理设置有利于防止任务重叠,有利以提高任务执行的精度
    //第三个参数是任务执行的周期
    //第二、三个参数的单位均为毫秒,也可配置定时器修改其单位
    //第四个参数代表任务是合作式还是抢占式
    //    一般取1就可以,更多用法请参考下文
    Sch.addTask(led1Update,0,1000,1);  //从第 0 毫秒开始闪烁 LED,每隔 1s, LED 状态改变一次
    Sch.addTask(led2Update,20,500,1);  //从第 20 毫秒开始闪烁 LED,每隔 0.5s, LED 状态改变一次

    Sch.start();//启动调度器
}

void loop()
{
    Sch.dispatchTasks();  // 执行被调度的任务,用调度器时放上这一句即可
}

// 把要调度的任务函数放下面

// 闪烁第 13 脚的 LED
void led1Update()
{
    if(g_led1State==0)
    {
        g_led1State=1;
        digitalWrite(13,HIGH);
    }
    else
    {
        g_led1State=0;
        digitalWrite(13,LOW);
    }
}

// 闪烁第 12 脚的 LED
void led2Update()
{
    if(g_led2State==0)
    {
        g_led2State=1;
        digitalWrite(12,HIGH);
    }
    else
    {
        g_led2State=0;
        digitalWrite(12,LOW);
    }
}

程序执行后,两个 LED 分别会以程序中指定的周期和时间闪烁。

更多功能

1. 添加抢占式任务

抢占式任务,简单说,就是优先级比正常任务(合作式任务)高的任务。在这个调度器中,抢占式任务可以打断正常任务,优先执行。

对于一些对时间精度要求较高的任务,可以将任务模式改为抢占式。

修改方法:

在添加任务的函数 Sch.addTask(任务名称,开始时间,执行周期,1) 函数中,将最后一个参数由 1 改为 0,即:

Sch.addTask(任务名称,开始时间,执行周期,1)

这样,该任务就成了抢占式任务。

2. 添加单次执行的任务

可以添加只执行一次的任务,在一段时间后执行。

只需把 Sch.addTask(任务名称,开始时间,执行周期,1) 中的执行周期改为 0 即可。

3. 删除任务

使用函数 Sch.addTask(任务名称,开始时间,执行周期,1) 时,会返回这个任务的 ID,将这个 ID 赋给一个变量。需要删除任务时,用删除任务函数 Sch.deleteTask(任务ID) ,就能把任务删除。

4. 调整被调度的任务数量

打开 TaskScheduler.h,找到 #define MAX_TASKS (10) ,将 10 修改为需要被调度的任务的数量。

5. 自动进入空闲模式

这个调度器能在没有任务的情况下自动进入空闲模式,以节省电量。不需要对程序进行其他修改。

6. 错误报告

打开 TaskScheduler.h,找到

//#define REPORT_ERRORS // Remove "//" to enable error report,

将前面的 // 去掉,打开错误报告功能。

然后,这条语句的下面,定义了相关错误代码,可根据情况修改。

最后,打开 TaskScheduler.cpp,找到函数 void Schedule::_reportStatus(void),在里面添加合适的错误报告代码即可。

欢迎大家对这个调度器进行测试,找出 bug 和需要优化的地方。

]]>
https://blanboom.org/2013/arduino-task-scheduler-library/feed/ 60 273