从零手写操作系统之RVOS软件定时器实现-08

news2024/10/7 18:26:48

从零手写操作系统之RVOS软件定时器实现-08

  • 定时器分类
  • 软件定时器的分类
  • 软件定时器设计与实现
    • 软件定时器调用流程
    • 增加对周期性定时任务支持
    • 测试
    • 优化点


本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而来,主要作为xv6操作系统学习的一个前置基础。

RVOS是本课程基于RISC-V搭建的简易操作系统名称。

课程代码和环境搭建教程参考github仓库: https://github.com/plctlab/riscv-operating-system-mooc/blob/main/howto-run-with-ubuntu1804_zh.md

前置知识:

  • RVOS环境搭建-01
  • RVOS操作系统内存管理简单实现-02
  • RVOS操作系统协作式多任务切换实现-03
  • RISC-V 学习篇之特权架构下的中断异常处理
  • 从零手写操作系统之RVOS外设中断实现-04
  • 从零手写操作系统之RVOS硬件定时器-05
  • 从零手写操作系统之RVOS抢占式多任务实现-06
  • 从零手写操作系统之RVOS任务同步和锁实现-07

定时器分类

  • 硬件定时器:芯片本身提供的定时器,一般由外部晶振提供,提供寄存器设置超时时间,并采用外部中断
    方式通知 CPU,参考硬件定时器一节的介绍。优点是精度高,但定时器个数受硬件芯片的设计限制。
  • 软件定时器:操作系统中基于硬件定时器提供的功能,采用软件方式实现。扩展了硬件定时器的限制,可以提供数目更多(几乎不受限制)的定时器;缺点是精度较低,必须是 Tick 的整数倍。

软件定时器的分类

在这里插入图片描述
本节采用超时函数运行在中断上下文环境中,因为比较简单。


软件定时器设计与实现

code/os/10-swtimer/os.h

/* software timer */
struct timer {
	//定时器超时后,任务执行器函数的入口地址
	void (*func)(void *arg);
	//参数
	void *arg;
	//超时计数器
	uint32_t timeout_tick;
};
//创建一个软件定时器任务 --- 任务执行器函数入口地址,函数实参,超时时间
extern struct timer *timer_create(void (*handler)(void *arg), void *arg, uint32_t timeout);
//删除一个软件定时器任务
extern void timer_delete(struct timer *timer);

code/os/10-swtimer/timer.c

  • timer.c文件是硬件定时器小节新增的,用于实现对硬件定时器模块支持
//最多支持同时存在10个软件定时器
#define MAX_TIMER 10
//采用数组存放软件定时器列表
static struct timer timer_list[MAX_TIMER];

code/os/10-swtimer/timer.c

//在原有的硬件定时器初始化逻辑之上,增加对软件定时器模块初始化支持
void timer_init()
{
    //初始化所有软件定时器任务
	struct timer *t = &(timer_list[0]);
	for (int i = 0; i < MAX_TIMER; i++) {
	    //将每个任务的func和arg清空
		t->func = NULL; /* use .func to flag if the item is used */
		t->arg = NULL;
		t++;
	}
    
    //--------------------下面是硬件定时器模块的初始化逻辑---------------------------------

	/*
	 * On reset, mtime is cleared to zero, but the mtimecmp registers 
	 * are not reset. So we have to init the mtimecmp manually.
	 */
	//硬件定时器模块初始化---传入interval间隔大约为1s
	timer_load(TIMER_INTERVAL);

	/* enable machine-mode timer interrupts. */
	//设置mie寄存器的MTIE位为1,开启时钟中断
	w_mie(r_mie() | MIE_MTIE);
}

/* load timer interval(in ticks) for next timer interrupt.*/
void timer_load(int interval)
{
	/* each CPU has a separate source of timer interrupts. */
	//获取当前hartId
	int id = r_mhartid();
	//设置mtimecmp寄存器的值为mtime寄存器的值+interval
	*(uint64_t*)CLINT_MTIMECMP(id) = *(uint64_t*)CLINT_MTIME + interval;
}

code/os/10-swtimer/timer.c

//创建软件定时器任务
struct timer *timer_create(void (*handler)(void *arg), void *arg, uint32_t timeout)
{
	/* TBD: params should be checked more, but now we just simplify this */
	if (NULL == handler || 0 == timeout) {
		return NULL;
	}

	/* use lock to protect the shared timer_list between multiple tasks */
	//使用锁来保护对共享软件定时器任务列表的操作---此处采用关中断实现
	spin_lock();
    //从软件定时器数组中寻找到第一个空位
	struct timer *t = &(timer_list[0]);
	for (int i = 0; i < MAX_TIMER; i++) {
		if (NULL == t->func) {
			break;
		}
		t++;
	}
	//如果没有剩余空位,那么返回NULL,表示软件定时器数组满了,创建失败
	if (NULL != t->func) {
		spin_unlock();
		return NULL;
	}
    //初始化软件定时任务
	t->func = handler;
	t->arg = arg;
	//_tick变量,记录系统从启动开始到现在为止,产生的时钟中断次数
	//_tick + timeout表示,从现在开始的第timeout次时钟中断后,软件定时器任务到期
	//由于本课程中默认设置时钟中断间隔为1s,所以等同于说: 软件定时器任务timeout秒后被调用执行
	t->timeout_tick = _tick + timeout;
    //释放锁
	spin_unlock();
	return t;
}

//--------------------下面是硬件定时器小节添加的逻辑---------------------------------

//用于记录
static uint32_t _tick = 0;

//发生定时器中断时,会调用该处理函数
void timer_handler() {
    //不断累加,记录系统从启动开始到现在为止,产生的时钟中断次数
	_tick++;
	printf("tick: %d\n", _tick);
	//本节在定时器中断处理函数中新增对软件定时器任务到期检查
	timer_check();
	//重置下一次时钟中断发生时间 -- 1s发生一次
	timer_load(TIMER_INTERVAL);
	//进行任务调度
	schedule();
}

code/os/10-swtimer/timer.c

void timer_delete(struct timer *timer)
{
    //使用锁来保护对共享软件定时器任务列表的操作---此处采用关中断实现
	spin_lock();
	struct timer *t = &(timer_list[0]);
	for (int i = 0; i < MAX_TIMER; i++) {
	    //定位到目标软件定时器,然后清空func和arg即可
		if (t == timer) {
			t->func = NULL;
			t->arg = NULL;
			break;
		}
		t++;
	}
    //释放锁
	spin_unlock();
}

软件定时器调用流程

在这里插入图片描述

  1. 时钟中断发生,调用timer_handler函数
  2. timer_handler函数中首先增加tick数,然后判断是否存在到期的软件定时器
  3. 如果存在,则直接在中断上下文环境中执行我们的软件定时器任务
/* this routine should be called in interrupt context (interrupt is disabled) */
static inline void timer_check()
{
	struct timer *t = &(timer_list[0]);
	for (int i = 0; i < MAX_TIMER; i++) {
		if (NULL != t->func) {
		    //判断是否有软件定时器任务到期
			if (_tick >= t->timeout_tick) {
				//发现一个到期的软件定时器任务,触发任务执行
				t->func(t->arg);

				/* once time, just delete it after timeout */
				//只会触发一次,触发后,就删除当前定时器任务
				t->func = NULL;
				t->arg = NULL;
                //跳出循环,一次时钟中断,最多执行一个到期的定时器任务
				break;
			}
		}
		t++;
	}
}

timer_check函数处理思路很简单,如下图所示:
在这里插入图片描述


增加对周期性定时任务支持

code/os/10-swtimer/timer.c

  • timer结构体中新增两个属性,用于记录当前任务是否为周期性任务和周期性任务触发间隔
/* software timer */
struct timer {
	void (*func)(void *arg);
	void *arg;
	//是否为周期性任务
	uint32_t period;
	//周期性触发间隔
	uint32_t period_time;
	//下一次触发时机
	uint32_t timeout_tick;
};
  • 定时任务创建:
    在这里插入图片描述
    在这里插入图片描述
  • timer_check函数中增加对周期性任务和一次性任务的区分处理
/* this routine should be called in interrupt context (interrupt is disabled) */
static inline void timer_check()
{
	struct timer *t = &(timer_list[0]);
	for (int i = 0; i < MAX_TIMER; i++) {
		if (NULL != t->func) {
			if (_tick >= t->timeout_tick) {
				t->func(t->arg);
                //非周期性任务,执行完直接删除
				if(t->period<=0){
                    /* once time, just delete it after timeout */
				    t->func = NULL;
				    t->arg = NULL;
					t->period=0;
					t->period_time=0;
				}else{
					//更新周期性任务下一次触发的时间
					t->timeout_tick=_tick+t->period_time;
				}
				break;
			}
		}
		t++;
	}
}

测试

user.c文件中,我们在任务0中创建定时器任务,然后测试其执行效果:

#include "os.h"
#define DELAY 4000

void timer_func(void *arg)
{
	if (NULL == arg)
		return;
	printf("%s\n",arg);
}

void user_task0(void)
{
	uart_puts("Task 0: Created!\n");
    //创建一个周期性任务
	struct timer *t1 = timer_create(timer_func,"task 1", 3,1);
	if (NULL == t1) {
		printf("timer_create() failed!\n");
	}
	//下面创建两个一次性定时任务
	struct timer *t2 = timer_create(timer_func, "task 2", 5,0);
	if (NULL == t2) {
		printf("timer_create() failed!\n");
	}
	struct timer *t3 = timer_create(timer_func, "task 3", 7,0);
	if (NULL == t3) {
		printf("timer_create() failed!\n");
	}
	while (1) {
		uart_puts("Task 0: Running... \n");
		task_delay(DELAY);
	}
}

void user_task1(void)
{
	uart_puts("Task 1: Created!\n");
	while (1) {
		uart_puts("Task 1: Running... \n");
		task_delay(DELAY);
	}
}

/* NOTICE: DON'T LOOP INFINITELY IN main() */
void os_main(void)
{
	task_create(user_task0);
	task_create(user_task1);
}

期望效果是任务1,在tick=3的倍数时,周期性执行;任务2在tick=5时,执行一次,任务3在tick=7时执行一次:
在这里插入图片描述


优化点

我们目前使用数组结构来管理我们的软件定时器列表,在软件定时器任务的创建,删除,查找过程中都涉及到大量遍历操作,虽然实现简单,但是效率很低。

如果要进行优化,有两个简单的思路:

  • 定时器按照超时时间排序,这样可以加速中断处理上下文判断是否存在到期的软件定时器任务
  • 链表方式对定时器实现管理,更加灵活

在这里插入图片描述

但是,光是单链表还不足以满足我们的需求,因为单链表的遍历效率同样很低。

可以考虑跳跃表(SkipList),通过多级链表形成类似B+ Tree的索引结构,加速CRUD的过程:

在这里插入图片描述
跳跃表实现相较于红黑树而言,实现更简单,并且查询复杂度也是O(logn),大家可以尝试在本节代码基础上,采用跳跃表作为软件定时器列表底层实现。


还有一个优化点就是大家可以尝试将定时任务执行挪动到任务执行上下文中去,可以考虑每个定时任务创建一个进程执行,或者采用生产者消费者模型:

  • 设置一个队列,当timer_check函数检查到有到期的定时任务时,就丢到队列中去
  • 然后可以是单进程或者多进程组成进程池,在队列中有任务中时,就唤醒生产者进行消费
  • 没有任务时,就挂起当前进程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/621890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python强制等待:如何优化你的Python技能

Python强制等待&#xff1a;如何优化你的Python技能 在Python编程中&#xff0c;强制等待是一种非常重要的程序设计方式。Python代码中的强制等待通常使用time.sleep()方法实现。在本文中&#xff0c;我们将详细介绍什么是Python强制等待&#xff0c;以及如何使用它来优化你的…

基于最近电平逼近的开环MMC逆变器MATLAB仿真模型

资源地址&#xff1a; 模型介绍&#xff1a; MATLAB21b版本 DC:12kV&#xff0c;N&#xff1d;12&#xff0c; 采用最近电平逼近调制&#xff0c;采用基于排序的均压方法&#xff0c;冒泡排序&#xff0b;桥臂电流方向判断。 连接负载&#xff0c;可以得到13电平相电压波形。…

Windows10下使用VS2019编译chromium

Windows10下使用VS2019编译chromium 工具设置代理cmd运行gclient配置VS的版本,环境变量设置下载源码生成编译工具 下载depot_tools,并配置环境变量,PATH下添加depot_tools的解压路径E:\src\depot_tools 设置代理 控制台管理员权限执行 git config --global http.proxy…

CenoOS连接 SQL Server

目录 1、问题&#xff1a;2、解决步骤3、拓展3.1 常用查询3.2 SQL Server 语句规则3.3 python调用 1、问题&#xff1a; 连接&#xff1a;ProviderSQLOLEDB.1;Persist Security InfoFalse;User IDXXX;passwordXXXXX;Initial CatalogXXXXX;Data SourceXXXXX; 解析&#xff1a;…

chatgpt赋能python:Python屏幕截图:完美的方法记录你的屏幕

Python屏幕截图&#xff1a;完美的方法记录你的屏幕 Python作为一种高级编程语言&#xff0c;被广泛用于开发各种应用程序和游戏&#xff0c;其中之一就是屏幕截图。 在本文中&#xff0c;我们将介绍使用Python进行屏幕截图的方法和技巧。 什么是屏幕截图&#xff1f; 屏幕截…

第六十八天学习记录:高等数学:导数(宋浩板书)

导数是微积分中的一个概念&#xff0c;描述了函数在某一个点上的变化率。具体地说&#xff0c;函数 f ( x ) f(x) f(x)在 x a xa xa处的导数为 f ′ ( a ) f(a) f′(a)&#xff0c;表示当 x x x在 a a a处发生微小的变化 Δ x \Delta x Δx时&#xff0c; f ( x ) f(x) f(x)对…

Golang每日一练(leetDay0090) 运算优先级、有效字母异位词

目录 241. 为运算表达式设计优先级 Different Ways to Add Parentheses &#x1f31f;&#x1f31f; 242. 有效的字母异位词 Valid Anagram &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 …

Vector源码

介绍 Vector是矢量队列&#xff0c;继承于AbstractList&#xff0c;实现了List, RandomAccess, Cloneable和Serializable接口Vector继承了AbstractList&#xff0c;实现了List接口&#xff0c;所以它是一个队列&#xff0c;支持相关的添加、删除、修改、遍历等功能Vector实现了…

chatgpt赋能python:Python的强制语句缩进解析

Python的强制语句缩进解析 什么是语句缩进 在其他编程语言中&#xff0c;我们通过使用花括号或者一些其他的符号来区分控制语句的范围。但在Python中&#xff0c;我们使用缩进来实现这个目的。这意味着任何控制结构的主体都必须按照要求正确缩进。 为什么Python强制要求使用…

【Java】JavaWEB核心要点总结:63

文章目录 1. JSP 和 Servlet 有什么区别2. JSP有哪些内置对象 分别是什么3. 详细讲解cookie session token4. 如果客户端禁止 了cookie &#xff0c;session 还能用吗5. session 的工作原理 1. JSP 和 Servlet 有什么区别 JSP&#xff08;Java Server Pages&#xff09;和Servl…

读改变未来的九大算法笔记06_图形识别

1. 人工智能研究人员在过去几十年中学到的最重要的教训之一 1.1. 看似智能的行为有可能从看似随机的系统中浮现出来 1.2. 如果我们有能力进入人脑&#xff0c;研究神经元之间连接的强度&#xff0c;其中绝大部分连接都会表现得很随机 1.3. 当作为集合体行动时&#xff0c;这…

javaScript蓝桥杯-----全球新冠疫情数据统计

目录 一、介绍二、准备三、⽬标四、代码五、完成 一、介绍 新冠疫情席卷全球&#xff0c;在此期间有很多免费的 API 和⽹站为⼈们提供了各个国家疫情数据的查询功能&#xff0c;这些免费公开的数据体现出了互联⽹作为信息媒介的优越性&#xff0c;帮助全球⼈⺠更好的了解⼀线疫…

电路模型和电路定律(3)——“电路分析”

小雅兰期末加油冲冲冲&#xff01;&#xff01;&#xff01; 复习之前的内容&#xff1a; 这样的连接方式是不可以的&#xff1a; 两个电压源&#xff0c;电压值不相同&#xff0c;是不能并联的 两个电流源&#xff0c;电流值不相同&#xff0c;是不能串联的 电流源也不能开…

浅谈Zuul、Gateway

一、Zuul Zuul是通过Servlet来实现的&#xff0c;Zuul通过自定义的ZuulServlet&#xff08;类似于Spring MVC的DispatcherServlet&#xff09;来对请求进行控制(一系列过滤器处理Http请求)。 所有的Request都要经过ZuulServlet的处理&#xff0c;三个核心的方法preRoute(),rou…

时钟频率的配置-DG32

时钟频率的配置-DG32 HXTAL&#xff1a;高速外部时钟&#xff0c;4到32MHz的外部振荡器&#xff0c;可为系统提供精确的主时钟。带有特定频率的晶体必须靠近两个HXTAL的引脚。和晶体连接的外部电阻和电容必须根据所选择的振荡器来调整&#xff1b; LXTAL&#xff1a;低速外部…

chatgpt赋能python:Python开发桌面应用全面介绍

Python开发桌面应用全面介绍 Python是一种非常万能的编程语言&#xff0c;也逐步发展成为一种适用于开发各种桌面应用程序的语言。Python开发桌面应用的优点是它可以快速开发&#xff0c;易于阅读和使用&#xff0c;同时具有很高的可扩展性和可维护性&#xff0c;因此越来越多…

chatgpt赋能python:Python开立方:简单快捷的计算方法

Python开立方&#xff1a;简单快捷的计算方法 如果你是一位程序员或者是一个正在学习编程的初学者&#xff0c;那么你一定会用到Python这个编程语言。Python作为一门多用途的编程语言&#xff0c;它有着简单易学、高效快捷、优雅简洁等优点。同时&#xff0c;在数据分析、人工…

Keras-3-实例2-多分类问题

1. 多分类问题&#xff1a; 1.1 路透社数据集加载 路透社数据集由路透社在1986年发布&#xff0c;包含46个不同的主题&#xff1a;某些主题样本较多&#xff0c;某些较少&#xff0c;但是训练集中每个主题都至少含有10个样本。 from keras.datasets import reuters(train_da…

ViewOverlay-加蒙层真的一种实现方式

一、ViewOverlay能实现什么&#xff1f; 在Android中&#xff0c;ViewOverlay是一个特殊的视图层&#xff0c;可以在一个视图的上方添加和管理附加的视图层&#xff0c;而不会干扰原始视图的布局和交互。它提供了一种方便的方式来在运行时添加、移除或修改视图层&#xff0c;而…

chatgpt赋能python:Python嵌入SEO

Python嵌入SEO Python是一种高级编程语言&#xff0c;由于其简单易学和广泛应用的特点&#xff0c;已经成为了许多工程师的首选语言。随着互联网发展的趋势&#xff0c;现代的SEO已经不再是简单的关键词填充和链接堆积&#xff0c;而是需要更复杂的优化方式&#xff0c;这时候…