hehung 发表于 2024-7-2 09:22

《嵌入式软件的时间分析》读书活动:2 第二章读书笔记-处理器基本知识

<p id="u18b025f4">第二章主要讲解的事一些处理器相关的知识,有助于嵌入式软件开发工程师了解一些处理器底层工作原理,让相关程序员能够利用底层工作原理优化代码。</p>

<p id="ud7ae0cd2">章节如下:</p>

<pre>
<code>第2章 处理器基础知识 13
2.1 处理器的构造 13
- 2.1.1 CISC 和 RISC 14
- 2.1.2 寄存器 14
2.2 代码执行 15
2.3 存储器寻址及其模式 17
- 2.3.1 对数据访问重要的寻址模式 18
- 2.3.2 跳转和调用的寻址模式 20
- 2.3.3 选择寻址模式 20
2.4 等待状态,突发访问 22
2.5 缓存 23
- 2.5.1 缓存结构和缓存行 24
- 2.5.2 组相联缓存及缓存逐出 25
2.6 流水线 27
2.7 中断 28
2.8 陷阱/异常 29
2.9 数据一致性 29
2.10 对比桌面处理器, 嵌入式处理器的特点 31
2.11 总结 32</code></pre>

<h1 data-lake-index-type="2" id="xWAfV">1. CISC和RISC</h1>

<p id="u6aabee8f">这是处理器的两种指令集:</p>

<ul>
        <li data-lake-index-type="0" id="u6d6ecf6f">CISC:复杂指令集,指所采用的复杂指令架构提供较高性能的处理器,一般此类处理器相对复杂,且成本较高,比如电脑芯片;</li>
        <li data-lake-index-type="0" id="uc84fe11c">RISC:精简指令集,此类处理器一般机器指令比较简单,需要的晶体管较少,一般单片机都是RISC指令集架构的,功耗也低。</li>
</ul>

<h1 data-lake-index-type="2" id="kopcP">2. 寄存器</h1>

<p id="ub341186c">每个处理器都有一些特定功能的寄存器,有特殊的功能:</p>

<ul>
        <li data-lake-index-type="0" id="ube2dd4d9">PC寄存器:也叫做程序寄存器,用来保存当前正在处理的指令的地址;</li>
        <li data-lake-index-type="0" id="ubaf66d0b">数据寄存器:用于逻辑运算、计算以及对存储器的读写;</li>
        <li data-lake-index-type="0" id="u81de1250">累加器:RISC指令集的特殊寄存器,用于大多数逻辑和算术运算;</li>
        <li data-lake-index-type="0" id="u8f7f8dab">地址寄存器:用于读取存储器的数据、将数据写入存储器、执行间接跳转或者间接调用函数;</li>
        <li data-lake-index-type="0" id="u11b859d7">状态寄存器:一组状态寄存器包含很多位,每个位都指示特定的状态,一般有如下状态(<strong>不同的内核存在一些区别</strong>);</li>
</ul>

<ul>
        <li>
        <ul ne-level="1">
                <li data-lake-index-type="0" id="u0b4532dd">IE:全局中断标志,用于禁止和使能全局中断;</li>
                <li data-lake-index-type="0" id="u52ec4bd2">IP:中断挂起标志,指示中断是否挂起;</li>
                <li data-lake-index-type="0" id="u87a95083">Z:零标志,上次逻辑或者算术运算的结果是否为0;</li>
                <li data-lake-index-type="0" id="u7243740b">C:用于标记逻辑或者算术运算的溢出或进位;</li>
        </ul>
        </li>
</ul>

<ul>
        <li data-lake-index-type="0" id="u162673e5">栈指针:记录一个地址,表示当前栈的栈顶。</li>
</ul>

<h1 data-lake-index-type="2" id="zOIif">3. 代码执行</h1>

<p id="u0b6519a7">微处理器会不断地处理机器指令,这些指令按照顺序从代码存放位置(FLASH或者RAM)加载到执行单元。只要没有跳转指令或调用函数的指令,一旦完成一条指令的处理,PC就会增加一个内存位置,指向下一条要执行的指令地址,该指令会被加载到执行单元,然后被解码和执行。</p>

<p id="u7c046b60">也就说代码执行其实也是顺序执行的,除非被跳转指令跳转到了其他地方,然后又会从新的地址开始顺序执行。</p>

<h1 data-lake-index-type="2" id="mX6ay">4. 寻址模式</h1>

<p id="u2f21e700">寻址模式描述了如何访问存储器,每次内存访问都需要定义要访问的地址以及应该如何处理该地址上的数据,包括存储数据到地址,从地址读出数据,跳转到地址或者从地址中调用子程序等。</p>

<p id="u78eef559">现在的处理器一般都是32bit的,所以最多可以提供4GB的空间,但是往往单片机处理器都没有这么大的空间,一般都是KB,或者MB,所以每次都是用32位寻址模式(远端寻址)的话,效率非常低,所以还需要一些其他的寻址模式,处理器不同,寻址模式会有区别。常用的有:</p>

<ul>
        <li data-lake-index-type="0" id="u5b7bd5c2">绝对寻址、远端寻址:地址包含的位数和地址总线宽度一致,可以访问正片区域的所有地址。</li>
        <li data-lake-index-type="0" id="u95f0a925">绝对寻址、近端寻址:地址包含的位数小于地址总线的宽度,只能访问有限的地址区域,但是更高效。</li>
        <li data-lake-index-type="0" id="u55017744">寄存器间接寻址:不直接包含任何直接地址信息,指示了可以找到所需地址的地址寄存器,因此可以在一个指令周期内执行,但是需要提前将地址信息写入地址寄存器As(若有)。</li>
        <li data-lake-index-type="0" id="uff696980">寄存器间接后增量寻址:和上一中类似,但是在从存储器中加载数据后,地址寄存器内容会自动加2(对于16位的处理器,对于32位处理器,会加4),即为&ldquo;后增量&rdquo;,访问后+2</li>
        <li data-lake-index-type="0" id="u39fda52b">基地址加偏移量寻址、间接寻址:也是通过地址寄存器间接访问,但是在访问前,会在此寄存器保存的地址上加上8位偏移,寄存器本身保存地址不受影响。</li>
</ul>

<p id="ue39f4055">上述寻址模式不是所有处理器都有的,根据处理器的不同,有不同的寻址模式。</p>

<h1 data-lake-index-type="2" id="QF5Wx">5. 等待状态和突发访问</h1>

<p id="ue63b007a">RAM访问速度快,Flash访问速度慢,所有大多数情况下,对FALSH的访问必须人为的放慢访问速度。</p>

<p id="u987bf9f3">每次访问存储器的时候都会访问地址,这就是一种开销。所以为了增大访问速度买很多存储器都提供突发访问,也就是从一个地址开始,一次性传输整个范围内的数据。</p>

<p id="ueafb1125">下图展示了多次单独访问和突发访问的时间开销,突发访问只需要请求一次即可。</p>

<div style="text-align: center;"></div>

<h1 data-lake-index-type="2" id="j0D6C">6. 缓存</h1>

<p id="u8e57d455">缓存的目的是减少存储器访问的开销。每个缓存区分为若干缓存行,每一行的大小是几十个字节,主存储器的大小一般是缓存的整数倍,向缓存传输数据或者从缓存读取数据都是突发访问,用于传输整个缓存行。</p>

<p id="u7b23850a">缓存的实现以及应用也是一个专业领域,本书只是简单的介绍了缓存的实现原理以及可能而定问题。</p>

<p id="u6b702ab9">书中用英飞凌的单片机举例,说明了多核对缓存区的访问可能导致的数据一致性的问题,简单的办法就是禁用共享存储区的缓存。</p>

<h1 data-lake-index-type="2" id="AQEiq">7. 流水线</h1>

<p id="uc04322c5">流水线(Pileline)即指令执行的步骤(不同的处理器执行的步骤会存在区别),一般存在如下几个步骤:</p>

<ol>
        <li data-lake-index-type="0" id="u3081d9bd">Fetch:取指,从存储器或缓存区加载指令</li>
        <li data-lake-index-type="0" id="u2f65a610">Decode:解码,解析操作码</li>
        <li data-lake-index-type="0" id="u424a33cd">Execute:执行命令</li>
        <li data-lake-index-type="0" id="u8d95b08f">Write-back:回写结果(若需)</li>
</ol>

<p id="u494c5bca">当一个命令进入解码阶段后(2),才可以提取下一个命令(1),前两个命令可以和第3,4个步骤同时执行,这种并行执行的方式可以提高处理器性能。</p>

<h1 data-lake-index-type="2" id="lLmjy">8. 中断</h1>

<p id="u507e7770">嵌入式开发人员对中断应该是相当熟悉的。</p>

<p id="uff0b8fa3">中断就是一段代码,不会被软件调用,而是由硬件事件触发。</p>

<p id="uc2686479">中断函数一般是无参数-无返回值的。</p>

<p id="u4e43111b">书中提到了&ldquo;几乎所有处理器上,进入中断服务程序后都会全局禁止中断。这是防止中断例程被其他(优先级更高的)中断再次中断的唯一方法,如果想要实现中断嵌套,则需要软件开启全局中断。&rdquo;</p>

<h1 data-lake-index-type="2" id="a1RUs">9. 陷阱/异常</h1>

<p id="ub82842e3">异常和陷阱其实都是异常,只是叫法不一样,与中断类似,但是比中断有更高的优先级,一般都是由处理器检测到了错误才会触发,每种处理器设计的异常是不一样的。</p>

<p id="udfcc970e">异常也会有处理程序可以由软件开发工程师实现,用来获取异常并处理。</p>

<h1 data-lake-index-type="2" id="NLsCI">10. 数据一致性</h1>

<p id="u0e4de3ab">数据一致性在嵌入式软件开发过程中至关重要。就算是在单核处理器上,也会存在数据一致性的问题,比如有如下代码,获取两个中断程序的总执行次数:</p>

<div style="text-align: center;"></div>

<p id="ud68d5e04">当高优先级的中断进入了24次之后,低优先级的中断进入,并且将变量counterISR中的数据读取放置到了寄存器中,此时高优先级的中断被执行,低优先级中断被打断执行,高优先级中断中读取counterISR值为24,+1 = 25写到counterISR中,然后退出中断,这时低优先级中断服务程序会继续执行之前的逻辑,从寄存器中读取counterISR为24,+1 = 25写回counterISR,这会导致counterISR丢失一个计数,从而产生数据一致性问题。</p>

<div style="text-align: center;"></div>

<p id="uef3053b9">所有非原子的访问或数据操作,即时间太长,无法在单个CPU周期内完成并且可以被中断的操作都面临这种风险。</p>

<p id="u6a9cac9e">有些硬件也会提供原子访问的机制。当访问宽度超过原子访问宽度的计时器时,处理器提供了特殊的访问机制来确保数据的一致性。</p>

<p id="u552ef9f1"><strong>一般软件常用的做法是对于这种可能出现数据一致性的代码之前,关闭全局中断,再在代码之后开启全局中断。</strong></p>

<h1 id="hr9zW">总结</h1>

<p id="u4e2e940e">本章主要介绍了处理器的架构、内存访问、缓存、寻址方式、流水线,中断、异常、数据一致性等,都是一些基本的知识,只能作为了解,因为该章节中的每一个小章节的内容,都需要单独专门讲解,想要了解的更清楚,需要查阅其他资料。</p>

<p id="u16b49aa3">&nbsp;</p>

Jacktang 发表于 2024-7-3 07:28

<p>当访问宽度超过原子访问宽度的计时器时,处理器提供了特殊的访问机制来确保数据的一致,这个应该所有处理器都是这么做的</p>
页: [1]
查看完整版本: 《嵌入式软件的时间分析》读书活动:2 第二章读书笔记-处理器基本知识