RT-Thread 中断管理学习(二)

news2025/1/21 18:46:32

中断的底半处理

RTT不对中断服务程序所需要的处理时间做任何假设、限制,但如图其它实时操作系统或非实时操作系统一样,用户需要保证所有的中断服务程序在尽可能短的时间内完成(中断服务程序在系统中相当于拥有最高的优先级,会抢占所有线程优先执行)。这样在发生中断嵌套,或屏蔽了中断源的过程中,不会耽误嵌套的其它中断处理过程,或自身中断源的下一次中断信号。

当一个中断发生时,中断服务程序需要取得相应的硬件状态或者数据。如果中断服务程序接下来要对状态或者数据进行简单处理,比如CPU时钟中断,中断服务程序只需对一个系统时钟变量进行加一操作,然后就结束中断服务程序。这类中断需要的运行时间往往都比较短。

但对于另外一些中断,中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将中断分割成两部分,上半部分底半部分

在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知,然后结束中断服务程序;接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称为底半处理。

以一个虚拟的网络设备接收网络数据包作为范例,并假设接收到数据报文后,系统对报文的分析、处理是一个相对耗时的,比外部中断源信号重要性小许多的,而且在不屏蔽中断源信号情况下也能处理的过程。

创建了一个nwt线程,这个线程在启动运行后,将阻塞在nw_bh_sem信号上,一旦这个信号量被释放,将执行接下来的nw_packet_parser过程,开始Bottom Half的事件处理。

rt_sem_t nw_bh_sem;

void demo_nw_thread(void *param)
{
	//首先对设备进行必要的初始化工作
	device_init_setting();
	nw_hb_sem = rt_sem_create("bh_sem", 0, RT_IPC_FLAG, PRIO);
	while(1)
	{
		rt_sem_take(nw_bh_sem, RT_WAITING_FOREVER);
		//接收到信号量后,开始真正的Bottom Half处理过程
		nw_packet_parser(packet_buffer);
		nw_packet_process(packet_buffer);
	}
}

int main(void)
{
    rt_thread_t thread;

    /* 创建处理线程 */
    thread = rt_thread_create("nwt",demo_nw_thread, RT_NULL, 1024, 20, 5);

    if (thread != RT_NULL)
        rt_thread_startup(thread);
}

demo_nw_isr是如何处理Top Half,并开启Bottom Half的。

void demo_nw_isr(int vector, void *param)
{
	//当network设备接收到数据后,陷入中断异常,开始执行此ISR
	//开始Top Half部分的处理,如读取硬件设备的状态以判断发生了何种中断
	nw_device_status_read();
	rt_sem_release(nw_bh_sem);
}

中断服务程序通过对一个信号量对象的等待和释放,来完成中断Bottom Half的起始和终结。
由于将中断处理划分为Top和Bottm两个部分后,使得中断处理过程变为异步过程。
必须认真考虑中断服务的处理时间是否大于给Bottom Half发送通知并处理的时间。

中断管理接口

为了把操作系统和系统底层的异常、中断硬件隔离开发,RTT把中断和异常封装为一组抽象接口。

在这里插入图片描述

中断服务程序挂接

系统把用户的中断服务程序(Handler)和指定的中断号关联起来,可调用如下的接口挂载一个新的中断服务程序:

rt_isr_handler_t rt_hw_interrupt_install(int vector,
                                        rt_isr_handler_t  handler,
                                        void *param,
                                        char *name);

调用这个接口后,当中断源产生中断时,系统将自动调用装载的中断服务程序。

  1. vector:挂载的中断号。
  2. handler:新挂载的中断服务程序。
  3. param:param会作为参数传递给中断服务程序。
  4. name:中断的名称。
  5. return :挂载这个中断服务程序之前挂载的中断服务程序的句柄

这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。

中断服务程序是一种需要特别注意的运行环境,它运行在非线程的执行环境下(一般为芯片的一种特殊运行模式(特权模式)),在这个运行环境中不能使用挂起当前线程的操作,因为当前线程并不存在,执行相关的操作会有类似打印提示信息,“Function [abc_func] shall not used in ISR”,含义是不应该在中断服务程序中调用的函数)。

中断源管理

通常在ISR准备处理某个中断信号之前,我们需要先屏蔽该中断源,在ISR处理完状态或数据以后,及时的打开之前屏蔽的中断源。

屏蔽中断源可以保证接下来的处理过程中硬件状态或者数据不会受到干扰。

可调用下面这个函数接口:

void rt_hw_interrupt_mask(int vector)

调用这个接口后,相应的中断会被屏蔽(通常当这个中断触发时,中断状态寄存器会有相应的变化,但并不送达到处理器进行处理)。

为了尽可能的不丢失硬件中断信号,可调用下面的函数接口打开屏蔽的中断源:

void rt_hw_interrupt_unmask(int vector);

全局中断开关

全局中断开关也称为中断锁,是禁止多线程访问临界区最简单的一种方式,即通过关闭中断的方式,来保证当前线程不会被其它事件打断(因为整个系统已经不会再响应那些可以触发线程重新调度的外部事件),也就是当前线程不会被抢占,除非这个线程主动放弃了处理器控制器。

rt_base_t rt_hw_interrupt_disable(void);

恢复中断也称为开中断,使能中断,它恢复了调用rt_hw_interrupt_disable()函数前的中断状态。如果调用之前是关中断状态,那么调用后依旧是关中断。

void rt_hw_interrupt_enable(rt_base_t level);

level:前一次rt_hw_interrupt_disable返回的中断状态

使用中断锁来操作临界区的方法可以应用于任何场合,且其他几类同步方式都是依赖于中断锁而实现的,可以说中断锁是最强大的和最高效的同步方法。只是使用中断锁最主要的问题在于,在中断关闭期间,系统不再响应任何中断,也就不能响应外部事件。
所以中断锁对系统的实时性影响非常巨大,当使用不当的时候会导致系统完全无实时性可言(系统偏离要求的时间需求),而使用得当,则会变成一种快速、高效的同步方式。

例如,为了保证一行代码(例如赋值)的互斥运行,最快速的方法是使用中断锁而不是使用信号量或互斥量。

level = rt_hw_interrupt_disable();
a = a + value;
rt_hw_interrupt_enbale(level);

在使用中断锁时,需要确保关闭中断的时间非常短,例如上面代码中的 a = a + value; 也可换成另外一种方式,例如使用信号量

rt_sem_take(sem_lock, RT_WAITING_FOREVER);
a = a + value;
rt_sem_release(sem_lock);

函数 rt_base_t rt_hw_interrupt_disable(void) 和函数 void rt_hw_interrupt_enable(rt_base_t level)一般需要配对使用,从而保证正确的中断状态。

在RTT中,开关全局中断的API支持多级嵌套使用,简单嵌套中断的代码如下。

#include <rthw.h>

void global_interrupt_demo(void)
{
	rt_base_t level0;
	rt_base_t level1;

	//第一次关闭全局中断,关闭之前的全局中断状态可能是打开的,也可能是关闭的。
	level0 = rt_hw_interrupt_disable();
	//第二次关闭全局中断,关闭之前的全局中断也是关闭的,关闭之后的全局中断还是关闭的。
	level1 = rt_hw_interrupt_disable();

	do_something();

	//恢复到全局中断第二次关闭之前的状态,所以本次enbale之后全局中断还是关闭的
	rt_hw_interrupt_enbale(level1);
	//恢复到全局中断第一次关闭之前的状态,这时候的全局中断状态可能是打开的,也可能是关闭的
	rt_hw_interrupt_enable(level0);
}

这个特性可以给代码的开发带来很大的遍历。例如在某个函数里关闭了中断,然后调用某些子函数,再打开中断。这些子函数里面也可能存在开关中断的代码。由于全局中断的API支持嵌套使用,用户无需为这些代码做特殊处理。

中断通知

当整个系统被中断打断,进入中断处理函数时,需要通知内核当前已经进入到中断状态。针对这种情况,可通过以下接口:

void rt_interrupt_enter(void);
void rt_interrupt_leave(void);

这两个接口分别用在中断前导程序和中断后续程序中,均会对rt_interrupt_nest(中断嵌套深度)的值进行修改。

每当进入中断时,可以调用rt_interrupt_enter()函数,用于通知内核,当前已经进入了中断状态,并增加中断嵌套深度(rt_interrupt_nest++);

每当退出中断时,可以调用rt_interrupt_leave()函数,用于通知内核,当前已经离开了中断状态,并减少中断嵌套深度(rt_interrupt_nest–)。注意不要在应用程序中调用这两个接口函数。

使用 rt_interrupt_enter/leave() 的作用是,在中断服务程序中,如果调用了内核相关的函数(如释放信号量等操作),则可以通过判断当前中断状态,让内核及时调整相应的行为。

例如:在中断中释放了一个信号量,唤醒了某线程,但通过判断发现当前系统处于中断上下文环境中,那么在进行线程切换时应该采取中断中线程切换的策略,而不是立即切换。

如果中断服务函数不会调用内核相关的函数(释放信号量等操作),这个时候,也可以不调用 rt_interrupt_enter/leave() 函数。

在上层应用中,在内核需要知道当前已经进入到中断状态或当前嵌套的中断深度时,可调用 rt_interrupt_get_nest() 接口,它会返回 rt_interrupt_nest。

rt_uint8_t rt_interrupt_get_nest(void);

在这里插入图片描述

中断与轮询

当驱动外设工作时,其编程模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题,并且这个问题在实时操作系统与分时操作系统中差异还非常大。

轮询模式本身采用顺序执行的方式:查询到相应的事件然后进行对应的处理。所以轮询模式从实现上来说,相对简单清晰。

例如往串口中写入数据,仅当串口控制器写完一个数据时,程序代码才写入下一个数据(否则这个数据丢弃掉)。相应的代码可以是这样的:

while(size)
{
	while(!(uart->uart_device->SR & USART_FLAG_TXE)); //发送完成置1,退出循环
	uart->uart_device->DR = (*ptr & 0x1FF);
	++ptr;
	--size;
}

在实时系统中轮询模式可能会出现非常大问题,因为在实时操作系统中,当一个程序持续地执行时(轮询时),它所有的线程会一直运行,比它低优先级的线程不会得到运行。

而分时系统中,几乎没有优先级之分,可以在一个时间片运行这个程序,然后在另外一段时间片上运行另外一段程序。

所以通常情况下,实时系统中更多采用的是中断模式来驱动外设
当数据达到时,由中断唤醒相关的处理线程,再进行后续的动作。

例如一些携带FIFO的串口外设,其写入过程是这样的:
在这里插入图片描述
线程先向串口的FIFO写入数据,当FIFO满时,线程主动挂起。串口控制器持续地从FIFO中取出数据并以配置的波特率发送出去。当FIFO中所有数据都发送完成时,将向处理器触发一个中断;当中断服务程序得到执行时,可以唤醒这个线程。这里举例的是 FIFO 类型的设备,在现实中也有 DMA 类型的设备,原理类似。

对于低速设备来说,运用这种模式非常好,因为在串口外设把FIFO中的数据发送出去前,处理器可以运行其它的线程,这样就提高了系统的整体运行效率。

但是对于一些高速设备,例如传输速度达到10Mbps的时候,假设一次发送的数据量是32字节,我们可以计算出发送这样一段数据量需要的时间是:(32 X 8) X 1/10Mbps = 25us。当数据需要继续传输时,系统将在25us后触发一个中断以唤醒上层线程继续下次传递。
假设系统的线程切换是8us,那么当整个系统运行时,对于数据带宽利用率将只有 25/(25+8) =75.8%。但是采用轮询模式,数据带宽的利用率则可能达到 100%。这个也是大家普遍认为实时系统中数据吞吐量不足的缘故,系统开销消耗在了线程切换上

发送数据量越小,发送速度越快,对于数据吞吐量的影响也越大。当一个实时系统想要提升数据吞吐量时,可以考虑的几种方式:

  1. 增加每次数据发送的长度,每次尽量让外设尽量多地发送数据。
  2. 必要情况下更改中断模式为轮询模式。同时为了解决轮询方式一直抢占处理机,其他低优先级线程得不到运行的情况,可以把轮询线程的优先级适当降低。

全局中断开关使用示例

在多线程访问同一个变量时,使用开关全局中断对该变量进行保护。

#include <rthw.h>
#include <rtthread.h>

#define THREAD_PRIORITY      20
#define THREAD_STACK_SIZE    512
#define THREAD_TIMESLICE     5

//同时访问的全局变量
static rt_uint32_t cnt;

void thread_entry(void *parameter)
{
	rt_uint32_t no;
    rt_uint32_t level;
    
    no = (rt_uint32_t) parameter;
    while(1)
    {
   		/* 关闭全局中断 */
        level = rt_hw_interrupt_disable();
        cnt += no;
        /* 恢复全局中断 */
        rt_hw_interrupt_enable(level);
        rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt);
        rt_thread_mdelay(no * 10);
    }
}

/* 用户应用程序入口 */
int interrupt_sample(void)
{
    rt_thread_t thread;

    /* 创建 t1 线程 */
    thread = rt_thread_create("thread1", thread_entry, (void *)10,
                              THREAD_STACK_SIZE,
                              THREAD_PRIORITY, THREAD_TIMESLICE);
    if (thread != RT_NULL)
        rt_thread_startup(thread);


    /* 创建 t2 线程 */
    thread = rt_thread_create("thread2", thread_entry, (void *)20,
                              THREAD_STACK_SIZE,
                              THREAD_PRIORITY, THREAD_TIMESLICE);
    if (thread != RT_NULL)
        rt_thread_startup(thread);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(interrupt_sample, interrupt sample);

由于关闭全局中断会导致整个系统不能响应中断,所以在使用关闭全局中断作为互斥访问临界区的手段时,必须需要保证关闭全局中断的时间非常短,例如运行数条机器指令的时间。

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

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

相关文章

论文研读-SIMD系列-利用BMI指令进行选择下推

利用位操作指令BMI在列存中进行选择下推 Selection Pushdown in Column Stores using Bit Manipulation Instructions 列存能够提供高效的压缩能力&#xff0c;所以当前分析型数据库系统都基于列存储。然而&#xff0c;查询处理时&#xff0c;压缩会面临解码速率的挑战。以往研…

亚马逊产品流量上不去怎么办?亚马逊产品流量入口有哪些?

众所周知流量对于跨境卖家们是很重要的&#xff0c;这影响了你产品的曝光度和转化率&#xff0c;那么如果亚马逊产品流量上不去怎么办&#xff0c;亚马逊产品流量入口有哪些&#xff1f; 亚马逊产品流量上不去怎么办&#xff1f; 1、优化产品标题和关键词 产品标题和关键词是…

mojo初体验

目录标题 mojo初体验试用地址变量定义参数可变性和所有权Structures后续 mojo初体验 试用地址 https://www.modular.com/get-started 与python基础语法很相似。 变量定义 let定义不可变变量var定义可变变量 参数可变性和所有权 下面是一个基本的函数&#xff1a; fn add…

第一章 计算机概述

1.冯诺依曼结构&#xff1a; 计算机由运算器、控制器、存储器、输入设备、输出设备五大部件组成 运算器和控制器称为CPU&#xff1b;CPU和存储器称为计算机主机&#xff1b;其余输入、输出设备、外存储器称为计算机外部设备采用二进制表示数据和指令 指令由操作码&#xff08;…

AJAX学习笔记1发送Get请求

传统请求有哪些方式,及缺点 传统请求有哪些? 1.直接在浏览器地址栏上输入URL. 2.点击超连接. <a href"/上下文/请求地址">超链接请求</a> ---->相对路径 <a href"http://www.baidu.com">超链接请求</a> ---->绝对路…

【Java 基础篇】Java StringBuffer详解:更高效的字符串处理

在Java编程中&#xff0c;字符串是一个常见的数据类型&#xff0c;用于存储文本信息。然而&#xff0c;与字符串相关的操作可能会导致性能问题&#xff0c;因为字符串是不可变的&#xff0c;每次对字符串进行操作都会创建一个新的字符串对象。为了解决这个问题&#xff0c;Java…

Windows命令行初步:更改配色、提示符以及编码方式

文章目录 启动和退出窗口标题和提示符命令行颜色更改编码 启动和退出 按下winR&#xff0c;调出运行窗口&#xff0c;输入cmd就可以进入命令行了。在Win10以前的系统种&#xff0c;如果在命令行中再输入一个cmd&#xff0c;就会再打开一个命令行。但最近的Win11版本中&#xf…

[管理与领导-66]:IT基层管理者 - 辅助技能 - 4- 乌卡时代(VUCA )的团队管理思维方式的转变

目录 一、乌卡时代人与公司的关系的转变 二、乌卡时代管理方式的转变 三、乌卡时代的管理与传统时代的管理比较 四、乌卡时代管理者的挑战 五、乌卡时代如何做好管理 六、个人能力要求 一、乌卡时代人与公司的关系的转变 在乌卡时代&#xff08;指虚拟办公、远程工作等数…

实景三维数字孪生系统(实景三维电子沙盘)

一、概况 实景三维数字孪生系统是一种基于虚拟现实技术的系统&#xff0c;紧紧围绕应急处置的核心业务&#xff0c;通过将真实世界的物体、场景和过程数字化&#xff0c;创建出一个与真实世界相对应的虚拟模型。它可以模拟真实世界中的各种情境和操作&#xff0c;使用户能够在虚…

PYTHON知识点学习-函数调用中returnprint

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是Aileen★。希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由 Aileen_0v0★ 原创 CSDN首发&#x1f412; 如需转载还请通知⚠ &am…

1779_树莓派小车电机驱动软件实现

全部学习汇总&#xff1a; GitHub - GreyZhang/little_bits_of_raspberry_pi: my hacking trip about raspberry pi. 前几天看了树莓派小车实现的硬件相关部件梳理视频&#xff0c;今天看了一段树莓派小车的电机驱动软件实现。 视频非常短&#xff0c;从里面能够提取出来的我感…

图上简单路径问题——转化为圆方树问题:abc318_g

https://atcoder.jp/contests/abc318/tasks/abc318_g 对原图建圆方树后&#xff0c;任意两点间的简单路径必然为其树上路径上方点对应其边双的点。 然后判断A&#xff0c;C路径上的方点是否会有B 圆方树&#xff1a; void dfs(int x) {dfn[x]low[x]tot; z.push(x); for(int …

【C++】学习STL中的list

❤️前言 大家好&#xff01;&#xff0c;今天为大家带来的一篇博客是关于STL中的list&#xff0c;内容主要包括list的介绍使用、list的模拟实现。以及list与vector的对比。 正文 list的介绍和使用 首先&#xff0c;让我们看看list的文档介绍&#xff1a; list是可以在常数范…

【C++深入浅出】类和对象上篇(类的基础、类的模型以及this指针)

目录 一. 前言 二. 面向对象与面向过程 2.1 面向过程 2.2 面向对象 三. 类的基础知识 3.1 类的引入 3.2 类的定义 3.3 成员变量的命名规则 3.4 封装 3.5 类的访问限定符 3.6 类的作用域 3.7 类的实例化 四. 类的对象模型 4.1 类对象的大小 4.2 类对象的存储方式 …

红队打靶:Narak打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现与端口扫描 第二步&#xff1a;Web渗透 第三步&#xff1a;tftp渗透 第四步&#xff1a;webdav利用 第五步&#xff1a;寻找敏感文件初步提权 第六步&#xff1a;motd利用提权 总结与思考 写在开头 本篇博客在自己的理解之上…

Windows下Redis的安装和配置

文章目录 一,Redis介绍二,Redis下载三,Redis安装-解压四,Redis配置五,Redis启动和关闭(通过terminal操作)六,Redis连接七,Redis使用 一,Redis介绍 远程字典服务,一个开源的,键值对形式的在线服务框架,值支持多数据结构,本文介绍windows下Redis的安装,配置相关,官网默认下载的是…

yo!这里是c++中的继承

目录 前言 概念定义 基类与派生类对象转换 作用域 派生类的默认成员函数 与友元&&与静态成员 菱形继承及菱形虚拟继承 多继承 菱形继承 虚拟继承 1.介绍 2.原理 继承总结 后记 前言 封装、继承、多态作为c的三大特性&#xff0c;在学完封装的有关内容之后…

OAuth2.0二 JWT以及Oauth2实现SSO

一 JWT 1.1 什么是JWT JSON Web Token&#xff08;JWT&#xff09;是一个开放的行业标准&#xff08;RFC 7519&#xff09;&#xff0c;它定义了一种简介的、自包含的协议格式&#xff0c;用于在通信双方传递json对象&#xff0c;传递的信息经过数字签名可以被验证和信任。JW…

python借助isinstance(item, (int, float))提取列表中的数字

如下一个列表[1,2,3,23, ,123] 借助isinstance(item, (int, float)) List [1,2,3,23, ,123] numbers [] # 遍历原始列表 for item in List:# 检查每个元素是否为数字&#xff08;整数或浮点数&#xff09;if isinstance(item, (int, float)):# 如果是数字&#xff0c;则添加…

Eclipse的安装(NEW~)

艾米&#xff0c;我擅长做很多事&#xff0c;但忘记你你并非其中一件。 随着IntelliJ IDEA在Java开发领域越来越广泛的被使用&#xff0c;Eclipse似乎快要退出舞台了。不过作为一款开源免费并拥有悠久历史的Java 开发IDE&#xff0c;总会有一批铁粉支持它&#xff0c;惦记着它。…