多线程必须要MMU才行?
两天看到有人说:计算机的多线程,必须要有MMU才行,否则系统不能正常运行。
MMU:Memory Management Unit,即内存管理单元,它是一个“好东西”,在大型的操作系统中,起到了关键的作用。
但是,并不是所有操作系统都需要MMU才行,我们嵌入式中很多常用的RTOS(实时操作系统)没有MMU一样可以实现多线程。
只是RTOS实现的多线程相对大型操作系统要简单一点,其原理也要简单的多。
下面就来围绕RTOS给大家大家关于RTOS的多线程的内容。
多线程
1.单核“单线程”
严格来说,单核处理器一次只能执行一条指令,也就是说只能“单线程”。(当然,多核处理器就不一样)
为了在单核处理器上运行多个线程,我们实际上需要定期在线程之间进行快速切换,以便用户感觉多个线程在并行运行。
比如处理器执行两个线程,处理器实际在两个线程之间来回切换,如下图:
2.处理器在线程之间切换,它是如何做到的?
我们说的单核处理器是“单线程”的,它有一组寄存器,我们就叫这组寄存器属于一个“线程”。
例如,计算两个数字的总和时:
//假设我们有两个整数:a和b
int c = a + b ;
实际发生的情况如下所示(当然,它取决于的MCU类型,但总体思路是相同):
# MIPS反汇编:
LW V0, -32744(GP) # "a" 的值从RAM加载到寄存器V0
LW V1, -32740(GP) # 值"b" 从RAM加载到寄存器V1
ADDU V0, V1, V0 # a、b值相加,结果保存到寄存器V0中
SW V0, -32496(GP) # 寄存器V0的值存储在RAM中(变量c所在的位置)
//假设我们有两个整数:a和b
int c = a + b ;
你会发现上面执行了4个动作,但是抢占式操作系统可以在任何时候抢占另一个线程,包括在这4个动作之间。
假如在这过程中有其他线程抢占了,其他线程同样抢占了当前线程V0、 V1,如果不对V0、 V1进行保存,那么下次回来执行当前线程,结果就会出错。
所以,针对当前这种问题,我们就需要在切换线程之前,对V0、 V1的数值进行保存,当下次切换到当前线程,再恢复V0、 V1的数值,大致流程如下:
大概意思就是:当我们需要从一个线程切换到另一个线程时,内核获得控制权,执行必要的内务处理(至少要保存和恢复寄存器值),然后将控制权转移到下一个线程以运行。
线程的堆栈
上面说的抢占位置,到底在哪里,每个线程保存在哪个寄存器值中?这就是线程的堆栈的内容。
在有MMU的操作系统中,(用户的)线程堆栈可以按需动态增长:线程需要的堆栈空间越多,线程堆栈就越多(如果内核允许)。
但是,我们一般的MCU却没有MMU这个“高端”的东西,所有RAM都静态映射到地址空间。因此,每个线程都会有用于堆栈的RAM空间,如果线程使用的RAM超过堆栈的数量,则会导致内存溢出或细微的错误。(实际上,每个线程的堆栈空间只是一连续数组空间)。
因此,当我们决定为每个线程分配多少堆栈时,我们只是估计可能需要多少堆栈,但是具体多少可能不是很清楚。
比如,如果这是一个具有多层嵌套调用的GUI线程,则可能需要数个千字节,但如果它是一个流水灯的小线程,则可能几十字节就足够了。
假设我们有三个线程,它们的堆栈消耗如下:
如上面所述,每个线程的寄存器值都保存在线程的堆栈中。线程的寄存器值集称为线程的“上下文”。如下图所示(线程A为在正在执行的“活动线程”):
请注意,在正在执行的线程A的上下文没有保存在堆栈中,堆栈指针指向线程A用户数据的顶部,并且当前处理器的寄存器专用于线程A。
当内核决定将控制权切换到线程B时,它将执行以下操作:
- 将所有寄存器值保存到堆栈中(保存到线程A堆栈的顶部);
- 将堆栈指针切换到线程B的堆栈顶部;
- 从堆栈(从线程B的堆栈顶部)恢复所有寄存器值;
此时,你会看到:
中断(ISR)抢占
上面在执行过程中,或进行上下文切换时,还可能会涉及到一个非常重要的内容:中断。
MCU通常具有外设:TIM、UART、 SPI、 CAN等,它们随时都能发生重要事件以触发中断。
中断条件是当当前正在执行的线程暂停时,处理器在一段时间内执行其他操作(Handles Interrupt),然后返回。中断可能随时触发,我们应该做好处理的准备。
中断处理程序称为ISR(中断服务程序):
中断可能具有不同的优先级,例如,如果触发了一些低优先级的中断,则当前正在执行的线程将暂停,并且ISR会获得控制权。然后,如果触发了某个高优先级中断,则当前正在执行的ISR将再次暂停,并为该高优先级中断运行一个新的ISR。
这样一来,完成后,控制权将返回到第一个ISR,并且在完成时,也会恢复被中断的线程。
重要的关键代码:
在线程活跃过程中,如果有重要的事情“关键的代码”,在这过程中如果中断发生,很容易导致意想不到的结果。
这部分关键的代码,我们需要要保护起来,通常我们的做法就是:在之前“关键代码”之前禁用全局中断,执行完之后,开始全局中断。
有点需要注意:
关闭全局中断,此时就不会相应中断,所以,“关键代码”不能太长。
中断堆栈
在上面说到一点,高优先级中断抢占低优先中断,就会出现一个问题:低优先级的代码需要和线程一样,用于保存数据的堆栈。
一般有两种方法:
- 使用被中断的线程堆栈;
- 为中断使用单独的堆栈空间;
1.使用被中断的线程堆栈
如果使用被中断的线程堆栈,就类似如下图:
这种情况存在你一个严重的问题,你知道是什么吗?
频繁中断,或者中断较多,线程自身的堆栈空间就会很快被使用完。
每个线程的堆栈都应该包含以下内容:
- 线程自己的数据;
- 线程的上下文;
- 用于执行最坏情况的ISR的数据。
因此,我们就需要换一种方法,为为所有ISR中断开辟单独的堆栈空间。
2.为中断使用单独的堆栈空间
为中断使用单独的堆栈空间大致如上图所示。
好了,本文讲述了上面几种关于抢占,以及相关的内容,你学会了几点?
- 分享
- 举报
-
浏览量:1212次2022-10-10 11:44:39
-
浏览量:2002次2020-04-11 10:22:37
-
浏览量:4180次2020-10-28 23:08:53
-
浏览量:6482次2020-10-28 23:03:59
-
浏览量:1733次2019-12-05 16:34:01
-
浏览量:5142次2021-09-15 13:50:22
-
浏览量:1447次2019-12-05 16:50:26
-
浏览量:1693次2020-08-07 17:02:17
-
浏览量:1654次2021-12-06 10:16:34
-
浏览量:3770次2020-08-19 16:34:45
-
浏览量:2592次2020-07-09 09:39:40
-
浏览量:1558次2024-05-24 17:53:56
-
浏览量:3111次2019-12-17 09:31:42
-
浏览量:14895次2020-11-13 15:08:38
-
浏览量:4712次2021-09-16 13:47:50
-
浏览量:1598次2020-08-10 09:42:52
-
浏览量:507次2023-12-21 10:45:56
-
浏览量:1811次2019-07-04 12:02:50
-
浏览量:781次2024-05-27 15:28:18
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
陌人丶
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明