嵌入式实时操作系统的设计与开发(三)

news2024/11/15 17:49:54

基本调度机制

用户在基于RTOS开发应用前,首先要创建线程。
aCoral中,用户创建一个线程时须指定用户希望采用的调度策略,例如,用户想创建一个周期性执行的线程并希望通过周期来触发多线程的调度。

//创建一个周期性的线程
acoral_period_policy_data_t data;
data.CPU = 0;
data.prio = 25;
data.timer = 1000;
acoral_create_thread_ext(test,ACORAL_PRINT_STACK_SIZE,0,NULL,NULL,ACORAL_SCHED_POLICY_PERIOD,&data);//调用线程创建接口

aCoral将线程创建分为两大类:

  1. 普通线程创建
  2. 特殊线程创建

普通线程

普通线程是指用户需要用通用调度策略进行调度的线程,例如,用户希望自己创建的线程采用FIFS的方式进行调度。
如果要创建通用调度策略的线程,就用普通线程创建函数acoral_create_thread(),它是一个宏,指向create_comm_thread()

#define acoral_create_thread(route,stack_szie,args,name,prio,CPU) create_comm_thread(route,stack_szie,args,name,prio,CPU);
#define acoral_create_thread_ext(route,stack_size,args,name,stack,policy,policy,data) create_thread_ext(route,stack_size,args,name,stack,policy,policy,data);

创建普通线程时,需要的参数分别为:执行线程的函数名,线程的堆栈空间,传入线程的参数,创建线程的名字,创建线程的优先级,绑定线程到指定CPU运行。
创建线程需要做的第一项工作:为线程分配内存空间,线程通过acoral_thread_t描述的,为该线程分配空间就是为TCB分配空间,其返回值是刚分配的TCB的指针。
分配过程通过函数acoral_alloc_thread()实现。
为TCB分配空间后,便是对TCB的各成员和调度策略控制块赋值,部分值是用户传入的参数,另一些值是在create_comm_thread()内部确定的。

acoral_id create_comm_thread(void (*route)(void *args), acoral_u32 stack_size,void *args, acoral_char *name, acoral_u8 prio, acoral_8 CPU){
	acoral_comm_policy_data_t policy_ctrl;
	acoral_thread_t *thread;
	// 分配tcb数据块
	thread = acoral_alloc_thread(); // 返回刚分配的线程指针
	if(NULL == thread){
		acoral_printerr("Alloc thread:%s fail\n",name);
		acoral_prints("No Mem Space or Beyond the max thread\n");
		return -1;
	}
	/*为tcb成员赋值*/
	thread->name = name;
	stack_size = stack_size&(~3);
	thread->stack_size = stack_size;
	thread->stack_buttom = NULL;
	policy_ctrl.CPU = CPU;
	/*设置线程的优先级*/
	policy_ctrl.prio = prio;
	policy_ctrl.prio_type=ACORAL_BASE_PRIO;
	thread->policy = ACORAL_SCHED_POLICY_COMM;
	thread->CPU_mask = ~1;
	return comm_policy_thread_init(thread,route,args,&policy_ctrl);
}

线程分配空间函数acoral_alloc_thread()是通过acoral_get_res()为资源控制块acoral_pool_ctrl_t分配空间。

typedef struct{
	acoral_u32 type; // 资源类型:线程控制块资源、事件块资源、时间数据块资源、驱动块资源等。
	acoral_u32 size; // 资源大小,一般就是结构体大小,如线程控制块的大小,用sizeof(acoral_thread_t)形式赋值。
	acoral_u32 num_per_pool;// 每个资源池对象的个数
	acoral_u32 num;// 已分配的资源池的个数
	acoral_u32 max_pools;// 最多可以分配多少个资源池
	acoral_list_t *free_pools,*pools,list[2]; //空闲资源池链表
	acoral_res_api_t *api;// 资源操作接口
#ifdef CFG_CMP
	acoral_spin_lock_t lock;
#denif
	acoral_u8 *name;//该类资源名称。
}acoral_pool_ctrl_t;

资源池管理的资源内存是从第一级内存系统(伙伴系统)分配的,为了最大使用内存,减少内存碎片,对象的个数、最大值、可分配内存等都是通过计算后由用户指定的。
例如,伙伴算法设定基本内存块的大小为1KB,资源的大小为1KB,用户一个资源池包含20个资源,这样计算下来分配20KB的内存空间,但是伙伴系统只能分配2i个基本内存块的大小,故会分配32KB,可包含32个资源对象,大于20个,所以每个资源池对象的个数更改为32。
所以,资源池真正可分配的对象的个数等于用户指定的资源对象的个数

线程初始化

创建线程的最后一步是对创建的普通线程进行初始化comm_policy_thread_init(),不同的调度策略,需要不同的初始化函数。
各调度策略需要什么线程初始化函数是在系统初始化时绑定的。

comm_policy_thread_init()主要是将通用策略控制块中的成员赋值给刚创建的线程的TCB的成员,然后调用acoral_thread_init()进行线程初始化。

acoral_id comm_policy_thread_init(acoral_thread_t *thread, void (*route)(void *args), void *args, void *data){
	acoral_sr CPU_sr;
	acoral_u32 prio;
	acoral_comm_policy_data_t *policy_data;
	policy_data = (acoral_comm_policy_data_t *)data;
	thread->CPU = policy_data->CPU;
	prio = policy_data->prio;
	if(policy_data->prio_type == ACORAL_BASE_PRIO){
		prio += ACORAL_BASE_PRIO_MIN;
		if(prio>=ACORAL_BASE_PRIO_MAX)
			prio = BASE_PRIO_MAX - 1;
	}
	thread->prio = prio;
	if(acoral_thread_init(thread,route,acoral_thread_exit,args) != 0){
		acoral_printerr("No thread Stack:%s\n",thread->name);
		HAL_ENTER_CRITICAL();
		acoral_release_res((acoral_res_t *)thread);
		HAL_EXIT_CRITICAL();
		return -1;
	}
	/*将线程就绪,并重新调度*/
	acoral_resume_thread(thread);
	return thread->res.id;
}

堆栈初始化

acoral_thread_init()的主要工作是做堆栈相关的初始化,包括堆栈空间、堆栈内容等。

acoral_err acoral_thread_init(acoral_thread_t *thread, void (*route)(void *args), void (*exit)(void), void *args){
	acoral_sr = cpu_sr;
	acoral_u32 stack_size = thread->stack_size;
	if(thread->stack_buttom==NULL){ //如果堆栈指针为NULL,则说明需要动态分配
		if(stack_size < ACORAL_MIN_STACK_SIZE)
			stack_size = ACORAL_MIN_STACK_SIZE;
		thread->stack_buttom = (acoral_u32 *)acoral_malloc(stack_size);// 分配堆栈,既然是动态分配,就有分配失败的可能
		if(thread->stack_buttom == NULL){
			return ACORAL_ERR_THREAD_NO_STACK;
		}
		thread->stack_size = stack_size;
	}
	thread->stack = (acoral_u32 *)((acoral_8 *)thread->stack_buttom+stack_size-4);// 模拟线程创建时的堆栈环境
	HAL_STACK_INIT(&thread->stack, route, exit, args);
	// 为线程创建CPU
	if(thread->CPU_mask == -1)
		thread->CPU_mask = 0xefffffff;
	if(thread->CPU < 0){
		thread->CPU = acoral_get_idle_maskCPU(thread->CPU_mask);
	}
	if(thread->CPU >= HAL_MAX_CPU){
		thread->CPU = HAL_MAX_CPU - 1;
	}	
	thread->data = NULL;
	thread->state = ACORAL_THREAD_STATE_SUSPEND;
	/*继承父线程的console_id*/
	thread->console_id = acoral_cur_thread->consoled_id;
	// 初始化线程的其它成员,如等待队列、就绪队列、延迟队列以及这些队列相关的自旋锁
	acoral_init_list(thread->waiting);
	acoral_init_list(thread->ready);
	acoral_init_list(thread->timeout);
	acoral_init_list(thread->global_list);
	acoral_spin_init(thread->waiting.lock);
	acoral_spin_init(thread->ready.lock);
	acoral_spin_init(thread->timeout.lock);
	acoral_spin_init(thread->global_list.lock);
	acoral_spin_init(move_lock);

	HAL_ENTER_CRITICAL();
	acoral_list_add2_tail(&thread->global_list,&acoral_thread_queue.head);// 将刚创建的线程挂到全局队列尾部
	HAL_EXIT_CRITICAL();
	return 0;
}

HAL_STACK_INIT()包括四个参数:堆栈指针变量地址、线程执行函数、线程退出函数、线程参数,无返回值。
从名字可以看出,HAL_STACK_INIT()是与硬件相关的函数,不同的处理器有不同的寄存器[寄存器个数、寄存器功能分配(程序指针、程序当前状态寄存器、连接寄存器、通用寄存器等)],这些寄存器体现了当然线程的运行环境,如果当前线程被其它中断或线程抢占,将发生线程上下文切换。
需要通过堆栈来保存被抢占线程的运行环境,先保存哪个寄存器,再保存哪个寄存器,需要根据处理器的结构来确定,HAL_STACK_INIT()就是用来规定寄存器保存顺序的。
ARM9 S3C2440的线程环境是通过R0~R15以及CPSR来保存的,即当发生上下文切换时,需要保持这16个寄存器的值(除R13(SP)外)。
故在堆栈初始化时,就得压入这么多寄存器来模拟线程的环境。

#define HAL_STACK_INIT(stack,route,exit,args) hal_stack_init(stack,route,exit,args)

// 用一个数据结构表示环境
typedef struct{
	acoral_u32 cpsr;
	acoral_u32 r0;
	acoral_u32 r1;
	acoral_u32 r2;
	acoral_u32 r3;
	acoral_u32 r4;
	acoral_u32 r5;
	acoral_u32 r6;
	acoral_u32 r7;
	acoral_u32 r8;
	acoral_u32 r9;
	acoral_u32 r10;
	acoral_u32 r11;
	acoral_u32 r12;
	acoral_u32 lr;
	acoral_u32 pc;
}hal_ctx_t;

由于是用C语言来模拟线程创建时的堆栈环境,所以用宏转换定义hal_stack_init()来实现HAL_STACK_INIT()。
R0~R7为通用寄存器,R8-R12为影子寄存器,R14(LR)为链接寄存器,R15(PC)是程序指针。
hal_stack_init()是线程相关的接口,包括四个参数,堆栈指针变量地址、线程执行函数、线程退出函数、线程参数,无返回值。

void hal_stack_init(acoral_u32 **stk, void (*route)(), void (*exit)(), void *args){
	hal_ctx_t *ctx = (hal_ctx_t *)*stk;
	ctx--;//获得堆栈模拟环境的基地址,堆栈是向下生长的,hal_ctx_t结构是向上的
	ctx =hal_ctx_t *((acoral_u32 *)ctx+1);//调整了4个字节,传进来的堆栈指针的内存本身就可以容纳一个数据。
	ctx->cpsr = 0x000001fL; //压入处理器状态寄存器
	ctx->r0 = (acoral_u32)args; //压入刚创建线程时需要传递的参数
	ctx->r1=1;
	ctx->r2=2;
	ctx->r3=3;
	ctx->r4=4;
	ctx->r5=5;
	ctx->r6=6;
	ctx->r7=7;
	ctx->r8=8;
	ctx->r9=9;
	ctx->r10=10;
	ctx->r11=11;
	ctx->r12=12;
	ctx->lr = (acoral_u32)exit;
	ctx->pc = (acoral_u32)route; //压入线程函数的入口地址
	*stk = ctx;
}

挂载线程到就绪队列

comm_policy_thread_init()是对通用调度策略下的线程初始化,最后一步是恢复线程,即是将新创建的线程挂载到一个就绪队列上。
恢复线程有两个接口:acoral_resume_thread()和acoral_rdy_thread(),前者比后者多一个acoral_sched()调度函数和一个判断;此外,acoral_resume_thread可由用户调度的,而acoral_rdy_thread只能由内核调用;acoral_resume_thread可能立即导致当前线程挂起。

void acoral_resume_thread(acoral_thread_t *thread){
	acoral_sr CPU_sr;
	acoral_8 CPU;
	if(!(thread->state&ACORAL_THREAD_STATE_SUSPEND))// 如果当前线程不处于suspend状态,则不需要唤醒
		return;
	acoral_enter_critical();
	acoral_rdyqueue_add(thread);
	acoral_exit_critical();
	acoral_sched();// 执行调度函数,对任务重新调度。
}
/*将线程挂到就绪队列上*/
void acoral_rdyqueue_add(acoral_thread_t *thread){
	acoral_rdy_queue_t *rdy_queue;
	rdy_queue = &acoral_ready_queues;
	acoral_prio_queue_add(rdy_queue,thread->prio,&thread->ready);// 将线程挂到优先队列
	thread->state &= ~ACORAL_THREAD_STATE_SUSPEND;
	thread->state |= ACORAL_THREAD_STATE_READY;
	acoral_set_need_sched(true); //线程所在的就绪队列发生了变化,有可能导致任务切换,所以要置为调度标志,让调度函数起作用。
}
void acoral_prio_queue_add(acoral_rdy_queue_t *array, acoral_u8 prio, acoral_list_t *list)
{
	acoral_queue_t *queue;
	acoral_list_t *head;
	array->num++;
	queue = array->queue + prio;// 根据线程优先级找到线程所在的优先级链表
	head = &queue->head;
	acoral_list_add2_tail(list, head);// 将该线程挂到该优先级链表上
	acoral_set_bit(prio, array->bitmap); // 置位该优先级就绪标志位
}

调度

调用acoral_sched(),一个普通线程创建的最后一步是调用内核调度函数acoral_sched(),从就绪队列中取出符合调度算法的线程依次执行。
真正意义上的多任务操作系统,都要通过一个调度程序(Scheduler)来实现调度功能,该调度程序以函数形式存在,用来实现操作系统的调度策略,可在内核的各个部分进行调用。
调用调度程序的具体位置又被称为是一个调度点,由于调度程序通常是由外部事件的中断来触发的,或者由周期性的时钟信号触发,因此调度点通常位于以下位置:

  1. 中断服务程序结束的位置。当用户通过按键向系统提出新的请求,系统首先以中断服务程序ISR响应用户请求,然后在中断服务程序结束时创建新的任务,并将新任务挂载到就绪队列末尾。
    接下来,RTOS就会进入一个调度点,调用调度程序,执行相应的调度策略。
    当I/O中断发生的时候,如果I/O事件是一个或多个任务正在等待的事件,则在I/O中断结束时刻,也将会进入一个调度点,调用调度程序,调度程序将根据调度策略确定是否继续执行当前处于运行状态的任务,或是让高优先级就绪任务抢占该任务。
  2. 运行任务因缺乏资源而被阻塞的时刻。例如,使用串口UART传输数据,如果UART正在被其它任务使用,这将导致当前任务从就绪状态转换成等待状态,不能继续执行,此时RTOS会进入一个调度点,调用调度程序。
  3. 任务周期开始或者结束的时刻。一些嵌入式实时系统往往将任务设计成周期性运行的,如空调控制器、雷达探测系统等,这样,在每个任务的周期开始或者结束时刻,都将进入调度点。
  4. 高优先级任务就绪的时刻。当高优先级任务处于就绪状态时,如果采用基于优先级的抢占式调度策略,将导致当前任务暂停运行,使更高优先级任务处于运行状态,此时也会进入调度点。

普通线程创建流程

在这里插入图片描述
首先为线程分配空间,然后根据创建线程的调度策略对线程TCB进行相关初始化,然后对线程的堆栈进行初始化,最后将创建的线程挂载到就绪队列上,供内核进行调度。

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

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

相关文章

视频播放中动画

CSS filter属性CSS的filter属性主要用于设置图像的视觉效果。语法&#xff1a;filter: none|blur()|brightness()|contrast()|drop-shadow()|grayscale()|hue-rotate()|invert()|opacity()|saturate()|sepia()|url();Filter 函数注意&#xff1a; 滤镜通常使用百分比 (如&#…

ArcGIS基础实验操作100例--实验49按分区划分栅格图层

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验49 按分区划分栅格图层 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;…

Power BI柱形图

在PowerBI中制作常见的柱形图&#xff0c;简单来说&#xff0c;柱形图就是利用水平的柱子表示不同分类数据的大小&#xff0c;与之类似的是条形图&#xff0c;它就是竖的柱形图&#xff0c;或者说把柱形图顺时针转动90度就成了条形图&#xff0c;使用以及作图方式类似&#xff…

媒体查询(@media语法、案例)详解

媒体查询media 语法1. 直接写在 CSS 样式中2. 针对不同的媒体设备&#xff0c;从外部链入不同的 stylesheets&#xff08;外部样式表&#xff09;使用 media 实现网页变色龙media 语法 media 可以直接写在 CSS 样式中&#xff0c;或者可以针对不同的媒体设备&#xff0c;从外部…

技术分享 | 一款功能全面的 MySQL Shell 插件

作者&#xff1a;杨涛涛 资深数据库专家&#xff0c;专研 MySQL 十余年。擅长 MySQL、PostgreSQL、MongoDB 等开源数据库相关的备份恢复、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生&#xff0c;为各大运营商及银行金融企业提供 MySQL 相关技术支持、MySQL 相关课…

[Java]异常处理

文章目录&#x1f97d; 异常概述&#x1f97d; 异常的分类&#x1f97d; 异常的处理&#x1f30a; 异常处理机制一&#xff1a;try-catch-finally&#x1f4a6; 语法结构&#x1f4a6; try-catch&#x1f4a6; finally&#x1f4a6; try-catch-finally处理异常的执行流程&#…

7.0、Linux-Vim编辑器以及常用命令详解

7.0、Linux-Vim编辑器以及常用命令详解 什么是 Vim 编辑器 -> Vim 是从 vi 发展出来的一个文本编辑器&#xff1b;代码补全、编译以及错误等方便编程的功能特别丰富&#xff0c;在程序员中被广泛使用&#xff1b;简单的来说&#xff0c;vi 是老式的字处理器&#xff0c;不过…

未知感知对象检测:从开放视频中学习你不知道的东西(学习笔记)

Unknown-Aware Object Detection: Learning What You Dont Know from Videos in the wild paper: https://arxiv.org/abs/2203.03800 code: https://github.com/deeplearning-wisc/stud the Wild 弄一个靠谱的目标检测器&#xff0c;完成OOD问题 什么是out of distribution (…

PHP Tools for Visual Studio 2019-2022 1.7 Crack

PHP Tools 是一个完整的 PHP 开发环境&#xff0c;位于单个软件包中。利用众所周知的行业标准 IDE 开发小型项目直至大型 PHP 应用程序。 该编辑器具有智能代码分析和快速抢占式代码完成功能。通过大量的导航功能、手边的本地化手册或快速重构操作来提高您的工作效率。 检查代码…

基于Amlogic 安卓9.0, 驱动简说(五):基于GPIO、LED子系统的LED驱动

一、篇头 本章介绍LED子系统的使用。使用LED子系统&#xff0c;可以轻松实现对LED&#xff0c;例如常见的闪烁和亮度控制功能。简单起见&#xff0c;本章先使用GPIO实现&#xff0c;在不模拟PWM的情况下&#xff0c;只能实现点亮和灭灯的效果&#xff0c;重点是介绍GPIO、LED子…

腾讯前端一面常考vue面试题汇总

vue2.x详细 1. 分析 首先找到vue的构造函数 源码位置&#xff1a;src\core\instance\index.js function Vue (options) {if (process.env.NODE_ENV ! production &&!(this instanceof Vue)) {warn(Vue is a constructor and should be called with the new keyword…

【Linux】Linux进程的理解 --- 进程描述符、状态、优先级、切换…

如果不改变自己&#xff0c;就别把跨年搞的和分水岭一样&#xff0c;记住你今年是什么吊样&#xff0c;明年就还会是什么吊样&#xff01;&#xff01;&#xff01; 文章目录一、冯诺依曼体系结构&#xff08;硬件&#xff09;二、操作系统&#xff08;软件&#xff09;1.操作…

HCIP第六天

文章目录一&#xff0c;实验要求二&#xff0c;搭建拓扑图三&#xff0c;配置IP地址和环回地址四&#xff0c;宣告OSPF五&#xff0c;抓取流量六&#xff0c;测试一&#xff0c;实验要求 所有到达目标的路径最优,互有备份二&#xff0c;搭建拓扑图 三&#xff0c;配置IP地址和环…

photoshop绘制网格线的几种办法和重复绘制处理加工

第一种 绘制一个十字 ,然后保存为图案,然后添加图层样式 图案叠加 重复 第二种 显示网格线 编辑-首选项-参考线网格线设置 默认是25, 25*5125毫米 image.png新建125x125毫米会发现非常对称 image.png画笔直接按shift自动锁定网格线进行绘制这样非常标准也很快,比图案还要灵活一…

60. 实战 Kaggle 比赛:图像分类 (CIFAR-10)【在colab上运行】

之前几节中&#xff0c;我们一直在使用深度学习框架的高级API直接获取张量格式的图像数据集。 但是在实践中&#xff0c;图像数据集通常以图像文件的形式出现。 本节将从原始图像文件开始&#xff0c;然后逐步组织、读取并将它们转换为张量格式。 我们之前对CIFAR-10数据集做了…

论文投稿指南——中文核心期刊推荐(生物科学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

vue开发环境配置Visual Studio Code配置和安装教程查询

方便前端vue开发&#xff0c;使用vs code插件安装详细教程&#xff0c;关于vs code在网络上查询相关的教程&#xff0c;插件安装如下图所示&#xff0c;大家发现常用的&#xff0c;好用的插件可以留言分享&#xff0c;或与我联系。 1 安装Vue语法高亮显示插件&#xff1a;vetur…

63.目标检测数据集

目标检测领域没有像MNIST和Fashion-MNIST那样的小数据集。 为了快速测试目标检测模型&#xff0c;我们收集并标记了一个小型数据集。 首先&#xff0c;我们拍摄了一组香蕉的照片&#xff0c;并生成了1000张不同角度和大小的香蕉图像。 然后&#xff0c;我们在一些背景图片的随机…

MSF弱点扫描

● 根据信息收集结果搜索漏洞利用模块 ● 结合外部漏洞扫描系统对大IP地址段进行批量扫描 ● 误判率、漏判率 VNC密码破解 use auxiliary/scanner/vnc/vnc_login● VNC无密码访问 use auxiliary/scanner/vnc/vnc_none_authRDP远程桌面漏洞 use auxiliary/scanner/rdp/ms12_…

【系统设计】直播架构分析

直播架构 1. 组成 三部分组成&#xff0c; 分别是 客户端&#xff08;主播端 观众端&#xff09; 、应用服务器集群 、 CDN 技术 2. 模块间交互方式 主播端 &#xff1a; 直播客户端开启直播间 —— 获取 CDN 推流地址 ——通过 CDN 协议推流到 CDN 服务器上 观众端&#x…